function escapeHtml(text) { return String(text || "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function sanitizeMarkdownUrl(url) { const s = String(url || "").trim(); if (!s) return ""; if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(s)) { const scheme = s.split(":", 1)[0].toLowerCase(); if (!["http", "https", "mailto"].includes(scheme)) return ""; } return s; } function renderMarkdown(md) { const raw = String(md || ""); const escaped = escapeHtml(raw); const blocks = []; const placeholder = (i) => `@@BLOCK_${i}@@`; const fenced = escaped.replace(/```([\s\S]*?)```/g, (_m, code) => { const html = `
${code.replace(/^\n+|\n+$/g, "")}
`; blocks.push(html); return placeholder(blocks.length - 1); }); let html = fenced .replace(/^### (.*)$/gm, "

$1

") .replace(/^## (.*)$/gm, "

$1

") .replace(/^# (.*)$/gm, "

$1

") .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_m, alt, url) => { const safeUrl = sanitizeMarkdownUrl(url); if (!safeUrl) return `[图片已拦截]`; return `${alt}`; }) .replace(/@\[(video)\]\(([^)]+)\)/g, (_m, _t, url) => { const safeUrl = sanitizeMarkdownUrl(url); if (!safeUrl) return `[视频已拦截]`; return ``; }) .replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, text, url) => { const safeUrl = sanitizeMarkdownUrl(url); if (!safeUrl) return `${text}`; return `${text}`; }) .replace(/`([^`]+)`/g, "$1") .replace(/\*\*([^*]+)\*\*/g, "$1") .replace(/\*([^*\n]+)\*/g, "$1"); html = html.replace(/\n{2,}/g, "\n\n"); html = html .split("\n\n") .map((p) => { if (p.startsWith("@@BLOCK_")) return p; if (/^/.test(p.trim()) || /^
");
      return `

${lines}

`; }) .join("\n"); blocks.forEach((b, i) => { html = html.replaceAll(placeholder(i), b); }); return html; }