/** * Markdown as markup that is safe to draw: the ONE pipeline every surface that * shows somebody's Markdown uses - the explorer's preview, and an app's release * notes on the admin panel's update review (#122). * * markdown-it with inline HTML (the GitHub/GitLab contract: ``, tables, * `
` work), then the preview sanitizer's `document` policy * (lib/sanitizeHtml.ts, DOMPurify): nothing that runs or submits survives. * * ⚠ markdown-it is an optional peer of this package, loaded when first needed. * When it cannot be loaded the answer is `null` - the caller shows the text as * text, never the raw string as markup. */ import { sanitizeHtml } from './sanitizeHtml'; type MarkdownIt = { render(src: string): string }; type MarkdownItCtor = new (opts: Record) => MarkdownIt; let renderer: Promise | null = null; function load(): Promise { if (!renderer) { renderer = (import(/* @vite-ignore */ 'markdown-it') as Promise) .then((mod) => { const m = mod as { default?: MarkdownItCtor } & MarkdownItCtor; const Md = (m.default ?? m) as MarkdownItCtor; return new Md({ html: true, linkify: true, breaks: true, typographer: true }); }) .catch(() => { // Not installed (an embed without the peer): ask again next time, it // may be a transient chunk-load failure. renderer = null; return null; }); } return renderer; } /** `text` rendered as Markdown and sanitized; null when markdown-it is unavailable. */ export async function markdownToSafeHtml(text: string): Promise { const md = await load(); if (!md) return null; return sanitizeHtml(md.render(text), 'document'); }