/* md.js — tiny, safe markdown subset for user-authored copy. * Supports: # headings, **bold**, *italic*, [links](url), - lists, blank-line paragraphs. * Everything is HTML-escaped FIRST, so authored text can never inject markup. */ const esc = (s) => String(s) .replace(/&/g, '&').replace(//g, '>') .replace(/"/g, '"').replace(/'/g, '''); export function renderMarkdown(src) { const lines = esc(src || '').split(/\r?\n/); const out = []; let para = [], list = null; const flushPara = () => { if (para.length) { out.push(`

${inline(para.join(' '))}

`); para = []; } }; const flushList = () => { if (list) { out.push(``); list = null; } }; for (const raw of lines) { const line = raw.trim(); if (!line) { flushPara(); flushList(); continue; } const h = line.match(/^(#{1,3})\s+(.*)$/); if (h) { flushPara(); flushList(); const n = h[1].length + 1; out.push(`${inline(h[2])}`); continue; } const li = line.match(/^[-*]\s+(.*)$/); if (li) { flushPara(); (list ||= []).push(`
  • ${inline(li[1])}
  • `); continue; } flushList(); para.push(line); } flushPara(); flushList(); return out.join('\n'); } function inline(t) { return t .replace(/\*\*([^*]+)\*\*/g, '$1') .replace(/\*([^*]+)\*/g, '$1') // links: only http(s) and site-relative, so no javascript: URLs .replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+|\/[^\s)]*)\)/g, '$1'); }