215 lines · 6.7 KB
import MarkdownIt from "markdown-it";
import type { MarkdownIt as MarkdownRenderer, StateCore } from "markdown-it";
import { highlight } from "sugar-high";
import { FilterXSS, safeAttrValue } from "xss";
import { linkTextReferences } from "./references";

export type MarkdownContext = {
  owner: string;
  repo: string;
  ref: string;
  /** Directory of the document inside the repo, used to resolve relative links. */
  basePath: string;
};

const ATTACHMENT_PREFIX = "/attachments/";
const ABSOLUTE_URL = /^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i;

function createRenderer(): MarkdownRenderer {
  const md = new MarkdownIt({
    html: true,
    linkify: true,
    typographer: false,
    highlight: (code) => `<pre class="sh-code"><code>${highlight(code)}</code></pre>`,
  });
  md.validateLink = (url) =>
    !/^(javascript|vbscript|data):/i.test(url.trim()) || /^data:image\/(png|gif|jpe?g|webp);/i.test(url.trim());
  return md;
}

const renderer = createRenderer();

const ALIGNED = ["align"];
const CELL = ["align", "colspan", "rowspan"];
const HEADING = ["align", "id"];

/** The raw HTML subset GitHub permits in markdown; everything else (scripts, styles, handlers) is stripped. */
const ALLOWED_HTML: Record<string, string[]> = {
  a: ["href", "title", "name", "id"],
  img: ["src", "alt", "title", "width", "height", "align"],
  picture: [],