Skip to content

Markdown 渲染引擎 ​

本文引用的源码文件

本文引用的文件

更新摘要 ​

变更内容

  • 增强了 CommonMark 合规性改进,包括复杂的行内代码跨度处理和多行内容支持
  • 改进了围栏检测机制,防止代码块内的 HTML 实体双重转义
  • 实现了 sophisticated 的行内代码跨度解析器,正确处理跨软换行的代码块
  • 优化了 HTML 实体转义逻辑,保护围栏代码块和行内代码不被误转义
  • 收紧了属性匹配正则表达式从任意空白字符到 [ \t],提高安全性
  • 增强了 escapeMustaches 函数,添加了跨行对称负测试用例确保 HTML 实体处理的正确性

目录 ​

  1. 引言
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能与优化
  8. 主题与样式集成
  9. 使用示例与扩展指南
  10. 故障排查
  11. 结论

引言 ​

本文件面向"Markdown 渲染引擎"的技术文档,聚焦侧边栏消息中的 Markdown 解析、安全消毒、代码块处理、Mermaid 图表渲染、表格工具链、按需加载策略、主题适配与安全输出。需要特别说明的是:当前仓库未实现 MathJax/KaTeX 数学公式渲染;若需支持 LaTeX,应通过自定义 marked 渲染器或后处理管线接入。

更新 本次更新重点增强了 CommonMark 合规性,改进了行内代码跨度和围栏检测机制,确保 HTML 实体的正确处理和避免双重转义问题。新增的 sophisticated 行内代码跨度解析器和优化的属性匹配正则表达式显著提升了安全性和准确性。

项目结构 ​

Markdown 渲染能力集中在侧边栏模块中,由以下关键文件组成:

  • src/side_panel/markdown-render.js:Markdown 解析、DOMPurify 消毒、Mermaid 渲染、表格工具链、导出与交互逻辑。
  • src/side_panel/libs-loader.js:Mermaid、jsPDF、html2canvas 的按需懒加载与主题配置。
  • libs/marked.min.js:第三方 Markdown 解析库(marked v15)。
  • libs/github-markdown-light.min.css:GitHub 风格 Markdown 基础样式。
  • src/shared/theme.css:亮/暗主题 Token 体系,覆盖 .markdown-body 在暗黑模式下的显示。
  • src/shared/theme.js:提供当前生效主题读取接口,供 Mermaid 初始化使用。
  • src/content/selection-toolbar.js:选区工具条中的流式 Markdown 渲染(存在未消毒直出风险点)。
  • agent/src/skill/markdown-loader.js:Agent 端 Skill 的 SKILL.md 解析(YAML frontmatter + 正文),非浏览器渲染层。
  • scripts/docsite/transform.mjs:文档站转换工具,包含增强的 CommonMark 合规性处理。
graph TB
A["消息内容<br/>formatMessageContent"] --> B["formatMarkdown<br/>marked.parse + DOMPurify"]
B --> C["还原 mermaid 占位符"]
B --> D["还原代码块占位符"]
B --> E["还原表格占位符"]
C --> F["renderMermaidCharts / renderMessageMermaid"]
F --> G["loadMermaid<br/>libs-loader.js"]
G --> H["mermaid.run()"]
E --> I["createTableHTML<br/>工具栏/复制/下载/导出图片"]
D --> J["addCodeCopyButtons<br/>Ctrl+点击复制"]
K["CommonMark 增强<br/>escapeStrayTags/escapeMustaches"] --> L["sophisticated 行内代码跨度处理"]
L --> M["围栏检测与 HTML 实体保护"]

图示来源

章节来源

核心组件 ​

架构总览 ​

Markdown 渲染流程分为三个阶段:

  1. 预处理:提取 mermaid、代码块、表格为占位符,避免被 marked 误解析。
  2. 解析与消毒:调用 marked.parse 生成 HTML,再用 DOMPurify 清理不安全元素。
  3. 后处理:还原 mermaid 容器、代码块容器、表格容器,并绑定交互事件。 更新 新增了 CommonMark 合规性处理阶段,确保行内代码跨度和围栏代码块的正确处理。
sequenceDiagram
  participant UI as "消息容器"
  participant MR as "markdown-render.js"
  participant CM as "CommonMark 增强"
  participant M as "marked"
  participant P as "DOMPurify"
  participant L as "libs-loader.js"
  participant MM as "mermaid"

  UI->>MR: formatMessageContent(text)
  MR->>CM: escapeStrayTags/escapeMustaches
  CM-->>MR: 安全的 Markdown 文本
  MR->>MR: 提取 mermaid/代码块/表格占位符
  MR->>M: parse(text, {gfm:true})
  M-->>MR: HTML
  MR->>P: sanitize(html)
  P-->>MR: 安全HTML
  MR->>MR: 还原 mermaid/代码块/表格
  MR->>UI: 插入 .markdown-body
  UI->>MR: renderMessageMermaid(messageDiv)
  MR->>L: loadMermaid()
  L-->>MR: mermaid 实例
  MR->>MM: run({nodes:[.mermaid]})
  MM-->>MR: SVG 图表
  MR->>UI: 添加工具栏/缩放/拖拽/导出

图示来源

详细组件分析 ​

Markdown 解析与消毒 ​

  • 解析选项:启用 breaks 与 gfm,使换行与 GitHub Flavored Markdown 语法生效。
  • 安全策略:禁止 style、iframe、script、object、embed、svg 等标签,以及常见 on* 事件属性;显式允许 target 属性以支持外链。
  • 降级方案:当 marked 不可用时,回退到简单正则替换,仅支持行内代码、加粗、斜体与换行。

更新 新增了 CommonMark 合规性处理,通过 escapeStrayTags 和 escapeMustaches 函数保护 HTML 标签和模板插值不被误转义。

flowchart TD
Start(["输入 Markdown"]) --> CM["CommonMark 增强<br/>escapeStrayTags/escapeMustaches"]
CM --> Extract["提取 mermaid/代码块/表格"]
Extract --> Parse{"marked 可用?"}
Parse --> |是| Marked["marked.parse(gfm)"]
Parse --> |否| Fallback["正则简化格式化"]
Marked --> Sanitize["DOMPurify.sanitize"]
Fallback --> Sanitize
Sanitize --> Restore["还原 mermaid/代码块/表格"]
Restore --> End(["输出安全 HTML"])

图示来源

章节来源

CommonMark 合规性增强 ​

新增功能 实现了 sophisticated 的行内代码跨度处理和多行内容支持,解决了以下关键问题:

  • 行内代码跨度解析:使用 mapOutsideInlineCode 函数实现长度感知的反引号配对定界,正确处理跨软换行的代码块。该解析器能够识别不同长度的反引号序列(如 `code` 和 code),并确保正确的配对。
  • 围栏检测优化:通过 mapProseLines 函数逐行判定围栏开关,确保围栏代码块内的内容不被误转义。空行刷新机制防止错误的配对发生。
  • HTML 实体保护:escapeStrayTags 函数保护围栏代码块和行内代码中的 HTML 标签,避免双重转义。属性匹配正则表达式已收紧为 [ \t],只匹配空格和制表符,提高了安全性。
  • 模板插值保护:escapeMustaches 函数保护 Vue 模板插值语法,防止 {{var}} 被编译。新增的跨行对称负测试用例确保了 HTML 实体处理的正确性。
flowchart TD
Input["Markdown 文本"] --> FenceCheck["mapProseLines<br/>围栏检测"]
FenceCheck --> ProseBlock["连续非围栏 prose 行"]
ProseBlock --> InlineCode["mapOutsideInlineCode<br/>行内代码跨度解析"]
InlineCode --> EscapeFn["escapeStrayTags/escapeMustaches"]
EscapeFn --> Output["安全的 Markdown 文本"]
subgraph "CommonMark 规则"
BacktickCount["等长反引号 run 配对"]
MultiLine["跨软换行支持"]
FenceProtection["围栏代码块保护"]
TightRegex["收紧的属性匹配正则 [ \\t]"]
end
InlineCode --> BacktickCount
InlineCode --> MultiLine
FenceCheck --> FenceProtection
EscapeFn --> TightRegex

图示来源

章节来源

Mermaid 图表渲染 ​

  • 提取与占位:将 ```mermaid 内容转为 <div class="mermaid"> 容器,data-raw-code 保存 URL 编码的原始源码,textContent 保存转义后的源码,避免尖括号被浏览器当作标签解析。
  • 按需加载:首次遇到 mermaid 时才加载 libs/mermaid.min.js,后续复用 Promise 缓存。
  • 渲染重试:单个容器渲染失败会最多重试两次;对语法错误直接返回失败并恢复原始内容。
  • 主题切换:rerenderMermaidForTheme 会清空已渲染 SVG,按当前主题重新初始化 mermaid 并渲染。
  • 交互工具栏:缩放、平移、复制 PNG、下载 PNG、查看源代码。
sequenceDiagram
  participant MR as "markdown-render.js"
  participant L as "libs-loader.js"
  participant MM as "mermaid"
  participant DOM as "DOM"

  MR->>DOM: 查询 .mermaid 节点
  MR->>L: loadMermaid()
  L-->>MR: mermaid 全局对象
  loop 每个 mermaid 容器
    MR->>MM: run({nodes:[container]})
    alt 成功
      MM-->>MR: SVG
      MR->>DOM: addMermaidControls(container)
    else 失败
      MR->>DOM: 恢复原始源码并显示错误提示
    end
  end

图示来源

章节来源

代码块处理与复制 ​

  • 代码块提取:匹配语言标识与内容,生成 <pre><code class="language-*"> 并包裹在 .code-block-container 中。
  • 复制机制:
    • 按钮复制:通过 document 捕获阶段的事件委托统一处理,避免大量逐元素监听。
    • Ctrl/Cmd + 点击:在 #chatContainer 上绑定一次委托,点击任意 code/pre 区域即可复制。
  • 安全性:代码内容在插入前进行 HTML 转义,防止 XSS。
flowchart TD
Input["代码块 Markdown"] --> Extract["提取 language + content"]
Extract --> Build["构建 pre/code + 复制按钮"]
Build --> Delegate["document 捕获阶段事件委托"]
Delegate --> Copy["navigator.clipboard 或 execCommand 降级"]
Copy --> Feedback["Toast 提示复制成功"]

图示来源

章节来源

表格工具链 ​

  • 解析:使用 splitTableCells 按 GFM 语义拆分单元格,支持单元格内字面竖线 \|。
  • 构建:生成带工具栏的表格 HTML,最后一列包含复制 Markdown、下载 Excel、导出图片按钮。
  • 功能:
    • 复制 Markdown 表格:优先从 data 属性读取,其次从 window.__tableBlocks,最终 fallback 从 DOM 提取。
    • 复制富文本表格:通过 ClipboardItem 写入 text/html,失败则临时 DOM + execCommand 降级。
    • 下载 Excel:CSV 格式,UTF-8 BOM,兼容 Excel。
    • 导出图片:html2canvas 将表格容器转为 JPEG 下载。
flowchart TD
TStart["表格 Markdown"] --> Split["splitTableCells 解析"]
Split --> Build["createTableHTML 构建工具栏"]
Build --> Actions{"用户操作"}
Actions --> |复制 MD| CopyMD["复制 Markdown 表格"]
Actions --> |复制富文本| RichCopy["ClipboardItem text/html"]
Actions --> |下载 Excel| CSV["CSV Blob 下载"]
Actions --> |导出图片| Img["html2canvas -> JPEG 下载"]

图示来源

章节来源

图片与链接处理 ​

  • 链接:DOMPurify 允许 target 属性,便于外链在新窗口打开;未实现 rel="noopener" 自动注入,建议在上层调用处补充。
  • 图片:marked 默认输出 <img>;当前未实现懒加载或外部链接安全检查;如需懒加载,可在渲染后遍历 img 节点设置 loading="lazy" 与 referrerpolicy。
  • 相对路径:当前未实现工作目录基址解析;建议在渲染前对 src/href 做规范化与白名单校验。

章节来源

数学公式渲染 ​

  • 现状:未集成 MathJax 或 KaTeX;marked 不原生支持 LaTeX 公式。
  • 建议方案:
    • 自定义 marked 渲染器:拦截 math 相关 token,输出占位符,再由后处理脚本用 MathJax/KaTeX 渲染。
    • 后处理管线:在 formatMarkdown 之后,扫描 DOM 中特定标记(如 <math> 或自定义类名)并替换为公式渲染结果。
  • 注意:引入公式渲染库时需考虑体积与首屏性能,采用按需加载与延迟渲染策略。

[本节为概念性说明,不涉及具体源码片段]

与 Agent 端 Skill Markdown 的关系 ​

  • Agent 端的 markdown-loader 用于解析 SKILL.md 的 YAML frontmatter 与正文,属于服务端技能加载逻辑,与浏览器侧 Markdown 渲染引擎无直接耦合。
  • 该模块提供 setMarkdownLoaderLang 以设置当前语言,翻译辅助函数 tr 基于 i18n 系统。

章节来源

依赖关系分析 ​

  • 运行时依赖:
    • marked:Markdown 解析。
    • DOMPurify:HTML 消毒。
    • mermaid:图表渲染(按需加载)。
    • html2canvas:表格导出图片(按需加载)。
    • jsPDF:PDF 导出(按需加载,当前未在 markdown-render.js 中直接使用)。
  • 样式依赖:
    • github-markdown-light.min.css:基础 Markdown 样式。
    • theme.css:暗黑模式覆盖与 Token 体系。

更新 新增了 CommonMark 合规性处理的依赖,通过 scripts/docsite/transform.mjs 提供增强的文本处理能力。

graph LR
MR["markdown-render.js"] --> M["marked.min.js"]
MR --> DP["DOMPurify"]
MR --> LL["libs-loader.js"]
LL --> MM["mermaid.min.js"]
LL --> HC["html2canvas.min.js"]
LL --> JP["jspdf.min.js"]
MR --> CSS["github-markdown-light.min.css"]
MR --> TH["theme.css"]
CM["CommonMark 增强<br/>transform.mjs"] --> MR

图示来源

章节来源

性能与优化 ​

  • 按需加载:Mermaid、jsPDF、html2canvas 仅在需要时动态注入脚本,避免首屏体积过大。
  • 渲染重试:Mermaid 单个容器渲染失败最多重试两次,减少偶发失败影响。
  • 事件委托:代码复制与表格工具栏按钮统一在 document 捕获阶段绑定一次委托,降低监听器数量。
  • 预转换 PNG:Mermaid 工具栏创建时异步预转换 SVG 为 PNG 缓存,提升复制/下载响应速度。
  • 尺寸限制:SVG 导出时对超大图进行缩放,避免 Canvas 内存溢出。
  • 新增 CommonMark 处理优化:通过长度感知的扫描器和围栏检测算法,避免不必要的重复处理。mapOutsideInlineCode 函数使用高效的字符串扫描算法,mapProseLines 函数通过空行刷新机制优化内存使用。

章节来源

主题与样式集成 ​

  • Mermaid 主题:libs-loader 中 mermaidThemeConfig 根据 getResolvedTheme() 返回 'dark' 或 'default',确保图表颜色与主题一致。
  • Markdown 主题:theme.css 针对 .markdown-body 在 html[data-theme="dark"] 下覆盖标题、链接、代码、表格、引用等样式,保证暗黑模式可读性。
  • 导出颜色:SVG 文本填充、Canvas 背景、html2canvas backgroundColor 均使用固定亮色值,确保导出物在任何宿主环境保持白底可读。
graph TB
TJS["theme.js<br/>getResolvedTheme()"] --> LC["libs-loader.js<br/>mermaidThemeConfig"]
LC --> MM["mermaid.initialize(theme)"]
CSS["theme.css<br/>[data-theme='dark'] .markdown-body"] --> MB[".markdown-body 样式覆盖"]

图示来源

章节来源

使用示例与扩展指南 ​

自定义渲染器 ​

  • 目标:在 Markdown 解析后对特定元素进行增强,例如为所有 <img> 添加懒加载与安全属性。
  • 步骤:
    1. 在 formatMessageContent 返回前,获取 .markdown-body 根节点。
    2. 遍历子节点,按需注入属性(如 loading="lazy"、referrerpolicy="no-referrer")。
    3. 对图片 src 进行白名单校验与相对路径规范化。
  • 参考位置:formatMessageContent 与 formatMarkdown 的输出结构。

章节来源

添加新语法支持 ​

  • 目标:支持任务列表、脚注等扩展语法。
  • 步骤:
    1. 确认 marked 是否原生支持;若不支持,编写自定义 renderer 或 post-processor。
    2. 在 formatMarkdown 中,先提取新语法块为占位符,再解析 HTML,最后还原为新语法 DOM。
    3. 为新增 DOM 绑定交互事件(如任务列表勾选状态)。
  • 参考位置:mermaid/代码块/表格的提取与还原模式。

章节来源

处理错误情况 ​

  • Mermaid 渲染失败:恢复原始源码并显示友好错误提示;日志记录原始内容与错误详情。
  • 图片导出失败:捕获异常并 Toast 提示;必要时降级为下载。
  • 剪贴板写入失败:降级为 execCommand 或自动下载。
  • 新增 CommonMark 处理错误:通过单元测试验证各种边界情况,确保 HTML 实体和模板插值的正确处理。包括跨软换行的行内代码跨度、围栏代码块保护和模板插值保护等场景。

章节来源

故障排查 ​

  • 流式渲染未消毒风险:选区工具条中存在 marked.parse 输出直出 innerHTML 的情况,可能引发 XSS。建议统一走 DOMPurify.sanitize。
  • Mermaid 未渲染:检查 libs/mermaid.min.js 是否可加载;确认 .mermaid 容器是否存在且 data-raw-code 正确。
  • 表格工具栏无效:确认 document 捕获阶段委托已绑定;检查按钮 class 名称是否正确。
  • 暗黑模式样式异常:确认 html[data-theme="dark"] 已应用;检查 .markdown-body 覆盖规则是否生效。
  • 新增 CommonMark 处理问题:如果 HTML 标签被误转义或模板插值被编译,检查 escapeStrayTags 和 escapeMustaches 函数的围栏检测逻辑。特别注意跨软换行的行内代码跨度处理和收紧的属性匹配正则表达式 [ \t] 的使用。

章节来源

结论 ​

本 Markdown 渲染引擎以 marked 为核心解析器,结合 DOMPurify 保障安全,通过 mermaid 实现图表可视化,并提供完整的代码块与表格工具链。整体采用按需加载与事件委托优化性能,并通过 theme.css 与 libs-loader 的主题配置实现亮/暗主题适配。

更新 本次更新显著增强了 CommonMark 合规性,通过 sophisticated 的行内代码跨度处理和围栏检测机制,确保了 HTML 实体的正确处理和避免双重转义问题。新增的 mapOutsideInlineCode 函数实现了长度感知的反引号配对定界,mapProseLines 函数提供了精确的围栏检测,而收紧的属性匹配正则表达式 [ \t] 进一步提升了安全性。这些改进特别是在处理复杂的多行内容和特殊字符场景时表现更加稳定,同时通过全面的单元测试确保了功能的可靠性。

未来可扩展数学公式渲染、图片懒加载与外链安全策略,进一步提升用户体验与安全性。同时,CommonMark 合规性的持续改进将为更复杂的 Markdown 语法支持奠定基础。