markdownHtml

tcmts.share.markdownHtml · #pqukpnt3 · term · runs on the server

uses tcmts.cli.pages.highlight, which uses tcmts.cli.pages.jsxPieces, which uses tcmts.core.ts.parse, which uses tcmts.core.ts.compiler, which uses process

Markdown as HTML for a page: tcm-page:<hash> links (a doc's links to code, a page's to other pages) open that definition's page, images kept in the codebase (tcm-page:<hash> too) are shown, each a link to its page, and TypeScript code blocks are highlighted. Other links are kept only to http(s), mailto and relative addresses (safeHref): a README, a comment or a contribution's description can't make a javascript: link.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { formatOf, isBytes } from "#codebase/tcmts/cli/files";
import { esc, highlight, IMAGE_TYPES } from "#codebase/tcmts/cli/pages";
import { codeHref, safeHref } from "#codebase/tcmts/share";
import { Marked } from "marked";
import type { Tokens } from "marked";

export function markdownHtml(cb: Codebase, md: string): string {
  return new Marked({
    gfm: true,
    renderer: {
      link({ href, tokens }: Tokens.Link) {
        const text = this.parser.parseInline(tokens);
        if (href.startsWith("tcm-page:")) {
          const name = cb.store.pathsOf(href.slice(9))[0];
          return name ? `<a href="${codeHref(name)}">${text}</a>` : text;
        }
        // (a link to anywhere but a page, a site or an address is its text: no javascript: URL is ever made a link)
        const safe = safeHref(href);
        return safe === undefined ? text : `<a href="${esc(safe)}">${text}</a>`;
      },
      image({ href, text, title }: Tokens.Image) {
        const alt = esc(text);
        const t = title ? ` title="${esc(title)}"` : "";
        if (!href.startsWith("tcm-page:")) return `<img src="${esc(href)}" alt="${alt}"${t}>`;
        // a file of the codebase: the image itself, in the page, a link to the file's page (it full size)
        const hash = href.slice(9);
        const rec = cb.store.getDef(hash);
        const name = cb.store.pathsOf(hash)[0];
        const format = rec?.kind === "file" ? formatOf(rec) : "";
        const src =
          rec && format === "svg" && !isBytes(rec)
            ? `data:image/svg+xml;charset=utf-8,${encodeURIComponent(rec.sourceText)}`
            : rec && IMAGE_TYPES[format] && isBytes(rec)
              ? `data:${IMAGE_TYPES[format]};base64,${rec.sourceText}`
              : undefined;
        if (!src) return alt;
        const img = `<img src="${src}" alt="${alt}"${t}>`;
        return name ? `<a class="figure" href="${codeHref(name)}">${img}</a>` : img;
      },
      code({ text, lang }: Tokens.Code) {
        const ts = lang && ["ts", "tsx", "typescript", "js", "jsx", "javascript"].includes(lang);
        return `<pre class="code">${ts ? highlight(text, [], new Set(), lang!.endsWith("x")) : esc(text)}</pre>\n`;
      },
      html({ text }: Tokens.HTML | Tokens.Tag) {
        return esc(text);
      },
    },
  }).parse(md) as string;
}

uses

used by