/** * Markdown 渲染器配置 */ import { SAFE_URI_SCHEMES } from './sanitizer.js'; // 简化的 Markdown 解析器(内联,不依赖外部 marked 库) // 支持基本 GFM 语法:标题、粗体、斜体、代码块、链接、列表、表格 function parseMarkdown(md: string): string { if (!md) return ''; let html = escapeForMd(md); // 代码块 ```...``` const codeBlockRe = new RegExp('```(\\w*)\\n([\\s\\S]*?)```', 'g'); html = html.replace(codeBlockRe, (_m, lang, code) => { return `
${code.trim()}
`; }); // 行内代码 html = html.replace(/`([^`]+)`/g, '$1'); // 标题 html = html.replace(/^######\s+(.+)$/gm, '
$1
'); html = html.replace(/^#####\s+(.+)$/gm, '
$1
'); html = html.replace(/^####\s+(.+)$/gm, '

$1

'); html = html.replace(/^###\s+(.+)$/gm, '

$1

'); html = html.replace(/^##\s+(.+)$/gm, '

$1

'); html = html.replace(/^#\s+(.+)$/gm, '

$1

'); // 粗体+斜体 html = html.replace(/\*\*\*(.+?)\*\*\*/g, '$1'); // 粗体 html = html.replace(/\*\*(.+?)\*\*/g, '$1'); // 斜体 html = html.replace(/\*(.+?)\*/g, '$1'); // 删除线 html = html.replace(/~~(.+?)~~/g, '$1'); // 链接 [text](url) html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, text, href) => { try { const safe = SAFE_URI_SCHEMES.has(new URL(href, 'https://placeholder').protocol); if (!safe) return `${text}`; } catch { /* allow relative */ } return `${text}`; }); // 图片 ![alt](url) html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '$1'); // 引用块 html = html.replace(/^>\s+(.+)$/gm, '

$1

'); // 水平线 html = html.replace(/^---+$/gm, '
'); // 无序列表 html = html.replace(/^[\-\*]\s+(.+)$/gm, '
  • $1
  • '); html = html.replace(/((?:
  • .*<\/li>\n?)+)/g, ''); // 有序列表 html = html.replace(/^\d+\.\s+(.+)$/gm, '
  • $1
  • '); // 换行 html = html.replace(/\n\n/g, '

    '); html = html.replace(/\n/g, '
    '); // 包裹在 p 标签中 if (!html.startsWith('<')) { html = '

    ' + html + '

    '; } return html; } function escapeForMd(str: string): string { return str .replace(/&/g, '&') .replace(//g, '>'); } export { parseMarkdown as marked };