🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

Mermaid 围栏渲染 XiaoBinGan/dsh-mermaid-fence

DeepSeek Harness
XiaoBinGan42 天前有提交已提供中文文档扫描:未发现敏感能力

把聊天里已写完的 mermaid 代码块渲染成 SVG 图

28.1/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/11 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add XiaoBinGan/dsh-mermaid-fence
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
dsh 原生插件 · chat
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 42 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🟢 扫描:未发现敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描未发现敏感能力:最多只发网络请求、读取自身元数据或只读文件,未发现执行命令、文件写入或敏感信息读取。

扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 可切换查看源码
  • 代码块兜底

在 dsh Web GUI 聊天里把已定稿的 ```mermaid 代码块渲染成 SVG 图——输出加固、可切换源码,还有代码块兜底,源码永不丢失。

README

由 DeepSeek 最新模型翻译生成
dsh-mermaid-fence 把 dsh Web GUI 聊天里的已定格的围栏会变成图。还在流式输出的围栏不会 —— 它会保持普通代码块,直到这一轮结束。这样,写了一半的图不会闪烁,不会每来一个分片就重新解析,也不会因为模型还没写完就报解析错误。 先把定位说清楚。 社区里已经有两个插件做这件事,而且都比这个插件做得多:AKS1st/dsh-mermaid 和 timedomain1/dsh-mermaid-renderer。想要懒加载、视口驱动加载、缩放、全屏查看器,或者报错回环,就装它们 —— 见与已有两个插件的区别。这个包只做一件它们不做的事:在 mermaid 渲染完成后加固它输出的 SVG,删掉 securityLevel: 'strict' 仍会放行的可联网元素。装之前先读那一节;加固是真的,但只是部分加固,那一节会讲清楚到底做到哪一步。 中文摘要 这是一个 dsh Web GUI 插件,把已定格的 mermaid 围栏渲染为 SVG 图;流式输出中的围栏保持为代码块。 先说清楚定位:社区里已经有更成熟的两个同类插件 —— AKS1st/dsh-mermaid(懒加载、视口驱动、全屏缩放、报错回环)和 timedomain1/dsh-mermaid-renderer(图/源码切换、缩放、独立查看器)。想要这些功能,请优先用它们。
展开完整文档(README 剩余 1 节)
dsh-mermaid-fence

把 dsh Web GUI 聊天里的已定格的围栏会变成图。还在流式输出的围栏不会 —— 它会保持普通代码块,直到这一轮结束。这样,写了一半的图不会闪烁,不会每来一个分片就重新解析,也不会因为模型还没写完就报解析错误。

先把定位说清楚。 社区里已经有两个插件做这件事,而且都比这个插件做得多:AKS1st/dsh-mermaid 和 timedomain1/dsh-mermaid-renderer。想要懒加载、视口驱动加载、缩放、全屏查看器,或者报错回环,就装它们 —— 见与已有两个插件的区别。这个包只做一件它们不做的事:在 mermaid 渲染完成后加固它输出的 SVG,删掉 securityLevel: 'strict' 仍会放行的可联网元素。装之前先读那一节;加固是真的,但只是部分加固,那一节会讲清楚到底做到哪一步。

中文摘要

这是一个 dsh Web GUI 插件,把已定格的 mermaid 围栏渲染为 SVG 图;流式输出中的围栏保持为代码块。

先说清楚定位:社区里已经有更成熟的两个同类插件 —— AKS1st/dsh-mermaid(懒加载、视口驱动、全屏缩放、报错回环)和 timedomain1/dsh-mermaid-renderer(图/源码切换、缩放、独立查看器)。想要这些功能,请优先用它们。

本插件唯一多出来的一步:在 mermaid 渲染完成后再加固一遍 SVG,删掉 securityLevel: 'strict' 仍会保留的可联网标签(img/iframe/object/embed/image/audio/video/source/track)、剥离 on 属性、丢弃非片段且非 data:image 的 href/src。但这不能阻止 mermaid 自己在渲染阶段发起的那次请求(已在 Chromium 实测),详见英文部分。

mermaid
graph TD
A[Start] --> B{Choice}
B -->|yes| C[Done]
B -->|no| D[Stop]

| 围栏状态 | 你会看到什么 |
| 已定格,源码有效 | SVG 图,外加一个开关,可以显示原始源码;宿主自带的复制按钮仍然可用 |
| 已定格,源码无效 | 原始代码块,外加一条可读的解析错误 |
| 仍在流式输出 | 原始代码块,原样不动 |
| 在非图内容上声明 mermaid | 原始代码块(mermaid 会拒绝它,并显示错误) |

这个插件是社区包,不是上游改动。DeepSeek Harness 不接受外部 pull request;它的 CONTRIBUTING.md 指出,发布途径是 dsh-plugin GitHub 话题,这个包就是这么分发的。这个仓库里没有任何东西去钩住内部渲染器的接缝 —— 它重写渲染后的对话 DOM,这既让它能作为独立插件安装,也是它主要的脆弱之处,见已知限制。

安装

不在 npm 上。公共 registry 对 dsh-mermaid-fence 返回 404,所以从 git 安装。已验证:这条命令会安装这个包,并准确解出下面六个文件,不需要构建步骤(client.js 已提交):

npm install github:XiaoBinGan/dsh-mermaid-fence
在 dsh 配置档里,同一个 specifier 会走宿主自己的安装器,把 bundle 加进配置档的 layer 列表:

dsh plugin --profile web add github:XiaoBinGan/dsh-mermaid-fence

(dsh plugin 这种写法是 git 安装 bundle 的官方途径;本 README 里那些数字所在机器上没有执行它,因为那台机器没装 dsh 命令行。执行的是 npm install 那种写法,结果就是下面引用的六文件目录树。)

安装后的目录树就是:

LICENSE README.md client.js cordis.patch.yml index.js package.json

client.js 是预构建好的浏览器产物——安装过程不跑构建步骤。package.json 声明了 dsh.bundle.patch: ./cordis.patch.yml,配置档列出这个包时,它就把包插进宿主的 bundle:

- insert:
- id: dsh-mermaid-fence
name: 'dsh-mermaid-fence'

行为

只处理已定稿的内容

宿主的 AssistantMarkdown 在 token 陆续到达时,会给消息根节点加上 data-streaming,等这一轮定稿后再把这个属性去掉。插件只扫描没有 [data-streaming] 祖先的围栏。不做任何推测性解析,流式中的围栏与宿主渲染出来的结果逐字节一致。

安全策略很严格,且不可配置

securityLevel: 'strict' 硬编码在 src/render.js 里。它不是选项,也不能通过配置放宽,因为围栏内容是模型输出——不可信输入。公开接口里没有 securityLevel 键,没有 htmlLabels 开关,也没有调色板:themeVariables 在渲染器内部从宿主自己的 CSS token 推导,渲染器只导出 render 和 refreshTheme 两个方法。

加固去掉了什么,没去掉什么——实测

securityLevel: 'strict' 是必要条件,但不充分,所以 mermaid 返回 SVG 后,还要过一遍 src/harden.js。这一遍会移除 script、iframe、object、embed、img、image、audio、video、source、track 这些元素,剥掉所有 on 属性,并丢弃任何 href/xlink:href/src/srcset/data/poster/action/formaction 值——只要它不是同文档片段(#id)或 data:image/ URI。

在真实 Chromium 里针对 mermaid 11.17.2 实测(下面这些数字都来自 Verification performed 里记录的运行):

| 实测项 | 结果 |
| 标签 在 strict 下 mermaid 产出的 SVG | ——处理函数被剥掉,元素及其 URL 都保留 |
| 换成插件自己的配置(flowchart.htmlLabels: false)后同样测试 | 没变化: 依然从 mermaid 里活下来 |
| 过完 harden.js 之后 | 不再有 img/image 元素;图里的 rect/path/text 图元都还在 |
| 在 strict 下,click C "https://example.com/tip" 指令会让 mermaid 输出 (没有 href);加固处理会丢掉这个属性,让锚点保持惰性 |

直说这个限制。 加固在 mermaid.render() 完成之后才运行,而 mermaid 构建输出时会把标记插入到活动文档里。所以在 Chromium 中, 这样的标签会在渲染调用期间、harden.js 还没看到字符串之前就发出真正的跨源请求——实测结果是:渲染期间一次请求,插入 mermaid 原始标记时再一次;而加固路径则是渲染期间两次、插入时零次。所以这一轮处理只是把元素从留在页面里的内容中移除(DOM 里不会留下活动的 元素,同一份标记重新渲染也不会再次抓取),但它拦不住 mermaid 自己那次瞬时抓取。唯一能拦住的办法,是一开始就拒绝渲染不受信任的标签;securityLevel: 'strict' 配上 htmlLabels: false 做不到这一点。

失败可见,绝不空白

如果 mermaid 拒绝这段源码,插件会原样保留宿主的代码块,并追加一条 role="status" 提示,写明 mermaid 自己报的错误。源码不会丢失,也不会有异常逃进宿主的控制台。失败的围栏会被标记,后续扫描不会重试。

主题绑定

颜色取自宿主自己的 --dsw- 自定义属性(通过 getComputedStyle 从 <body> 上读取),所以图表在两种主题下都清晰可读,插件不必自己维护一套配色。深色模式通过 <body> 上的 data-ds-dark-theme 属性检测。该属性一翻转,插件就重新读取 token,并重新渲染发生变化的那棵子树里已挂载的图表。

源码访问

每张图表都保留宿主的横幅——这样宿主自己的复制按钮仍然可用——并加一个开关,用来显示原始围栏文本。

销毁

插件创建的一切都在 apply 内通过 ctx.effect 注册,每次注册都返回自己的清理逻辑:MutationObserver(断开连接)、防抖定时器(清除)、arm(销毁,释放所有引用)以及注入的 (移除)。销毁由一个测试验证:在跑完清理之后挂载一个新的围栏,断言它没有被渲染。

已知限制

- 包体积。 client.js 有 3,461,602 字节(3.3 MiB / 3.5 MB 十进制;gzip 后 962,140 字节),因为 mermaid 被内联了。见为什么 mermaid 要内联。
- DOM 后处理,不是渲染器 arm。 插件改写渲染后的对话 DOM,而不是挂到宿主的 renderCode() 接缝上。这正是它能作为独立社区插件安装的原因,但也意味着图表是在 React 渲染之后才插入的,而且宿主将来若改动代码块 DOM(.md-code-block、[data-code-block-banner] 和 [data-code-block-content] 契约),就需要更新这个插件。
- 语言检测读取横幅文本。 宿主把 code.className 留空,只把围栏语言放进横幅的 infostring,所以插件读的就是它;对于未声明语言的围栏,回退到取第一个关键字。未声明语言的围栏,如果第一个关键字不在识别集合里,就不渲染。
- jsdom 无法对 SVG 做布局。 单元测试对 getBBox、getScreenCTM 和 getComputedTextLength 打了桩,所以它们只能证明渲染管线跑得通、产出的结构符合预期——不能证明几何。几何和净化在真实 Chromium 里验证(见下文)。
- 一个围栏一张图,没有交互。 点击处理器和脚本化图表按设计禁用(securityLevel: 'strict')。

与已有的两个插件有何不同

两者都是 MIT 协议,渲染工作都比这个包做得多。哪个合你用,就用哪个。

| | AKS1st/dsh-mermaid | timedomain1/dsh-mermaid-renderer | 本包 |
| mermaid 加载 | 懒加载,走宿主自己的路由 | 首次画图时懒加载 | 内联在 client.js 里(3.5 MB) |
| 何时渲染 | 视口驱动,一次排队一个 | 扫描时 | 稳定后 |
| 缩放 / 全屏 | 全屏浮层、滚轮缩放、拖拽平移 | 卡片缩放 0.25–4x、独立查看器、在新标签页打开 | 无 |
| 源码视图 | 横幅复制按钮 | 图表/源码切换 + 复制 | 源码切换,保留宿主的复制按钮 |
| 渲染失败 UX | 错误条,带复制报告和「发送给 AI」 | 重试提示 | 一行提示,写明 mermaid 的报错 |
| 语言覆盖 | infostring mermaid | mermaid / mmd,外加关键字检测 | infostring mermaid/mmd,外加关键字检测 |
| mermaid 返回之后 | 插入 mermaid 的 SVG | 插入 mermaid 的 SVG | 先加固 SVG(见上文,包括那条限制) |

所以:想要打磨好、懒加载的查看器,还带错误报告闭环,就用 AKS1st/dsh-mermaid;想要每张卡片独立缩放和独立查看器,就用 timedomain1/dsh-mermaid-renderer。只有当你就是冲着渲染后的加固这一步来的,才装这个——而且先读那条限制,因为它并没有堵住 mermaid 自己在渲染期间发起的请求。

为什么把 mermaid 内联

宿主的模块表把 dsh.client 这一行解析到包的 ./client 导出。mermaid 不是平台模块,所以不能来自 require(...)。

加载器确实通过 require.async 支持包内动态分块,src/browser-entry.js 也用了动态 import('mermaid')——但 esbuild 的 iife 格式没有代码分割,所以那次 import 变成单个输出文件里一个懒求值的分块,而不是单独的文件。单独的分块 URL 由宿主的 bundle roster 拼出来,而一个独立的社区插件没有 roster 行,所以真正拆分构建在安装时无法解析。

因此,内联是唯一无需宿主侧集成就能安装的机制——这也是这个包主要的实际成本。保留动态 import,是因为它仍会推迟 mermaid 的模块主体——以及约 3 MB 的图表定义——直到插件应用时才加载。

已执行的验证

下面每条命令都在产出这份 README 的机器上、于本包目录内运行。计数和大小均复制自这些运行的真实输出,不是估算。环境:Node v24.7.0、npm 11.5.1、mermaid 11.17.2(node_modules/mermaid/package.json)、Playwright 的 Chromium。

| 命令 | 结果 |
| npm test | 26 tests, 25 pass, 0 fail, 1 skipped |
| npm run test:browser (real Chromium) | 2 pass |
| npm run check (node --check client.js && node --check index.js) | exit 0 |
| wc -c client.js | 3,461,602 bytes |
| gzip -c client.js \| wc -c | 962,140 bytes |
| npm pack --dry-run | 6 files, 967.8 kB packed / 3.5 MB unpacked (the tarball includes this README, so the packed figure moves by well under a kilobyte whenever the README is edited) |
| npm install github:XiaoBinGan/dsh-mermaid-fence (clean dir) | exit 0, 1 package, the six files above, client.js byte-identical (sha256 match) |

跳过的测试是 a label containing HTML is escaped rather than executed:mermaid 的 HTML 标签清理器在 jsdom 下永远不会结束(没有布局,也没有真正的 HTML 解析器完成),所以这条断言被移到了真实浏览器测试套件,而不是用桩来伪造。

在真实 Chromium 中验证(tests/browser.test.mjs,通过 HTTP 驱动 example/harness.html)。测试夹具会报告自己的测量值,所以下面是某次运行的真实数字,而不只是通过/失败:

- 已完成的围栏会生成带非零布局盒的 SVG(在测试夹具中为 250 × 335)、4 个节点组、3 条边,以及一条具有真实 getTotalLength() 几何值(46)的边路径;
- 流式围栏生成零个图表;
- 无效围栏生成零个图表和一条错误提示,引用 mermaid 自己的消息(No diagram type detected matching given configuration…);
- 宿主的 --dsw- token 被读取,图表文字颜色解析为真实值(rgb(27, 28, 31));
- 宿主的复制按钮仍可访问,源码切换正常工作;
- 任何情况下都没有未捕获的页面错误;
- 恶意标签()不会产生 、不会产生 、不会产生 on*= 属性,也不会弹出对话框。

上文记录的加固限制是单独测量的,用一个一次性的 Chromium 脚本(两个服务器:页面源和一个统计命中次数的「攻击者」源)。在 mermaid 11.17.2 下、使用插件的精确配置,标签 <img src="http://<attacker>/x.gif"> 产生的结果是:原始路径——在 mermaid.render() 期间有 1 次跨源请求,插入 mermaid 的 SVG 时 1 次,再插入时又 1 次再说一次,DOM 里还剩 2 个 元素;加固路径下——渲染期间 2 个,两次插入时都是 0,最后剩 0 个 元素。渲染期间那次短暂的请求来自 mermaid,不是这个插件,这个插件拦不住。

自己打开测试台

npx serve . # or any static server
open http://localhost:3000/example/harness.html

测试台会把真正的 client.js 挂到一份手写的围栏 DOM 上(一个已定型、一个流式、一个非法、一个恶意),还带主题开关,方便检查浅色和深色。

开发

npm install
npm run build # regenerate client.js with esbuild
npm test # jsdom suite
npm run test:browser # real Chromium (needs: npx playwright install chromium)

目录结构

client.js built browser artifact (committed; mermaid inlined)
index.js Host face (no services, no listeners)
cordis.patch.yml bundle insert
src/
browser-entry.js entry point of the built artifact
client.js the Cordis surface: apply, effects, observer
arm.js the DOM walk: settled gate, mount, fallback, dispose
render.js the renderer; hardcoded strict security
detect.js language/source recognition
theme.js host token → mermaid themeVariables
harden.js post-render SVG hardening (removes fetch/execute vectors)
styles.js token-driven stylesheet
i18n.js UI strings
example/harness.html browser harness
tests/ node:test suites over real DOM and real mermaid

许可证

MIT
预览分享卡片长什么样 ↓XiaoBinGan/dsh-mermaid-fence
分享 · DeepSeek Harness Hub
XiaoBinGan/dsh-mermaid-fence

把聊天里已写完的 mermaid 代码块渲染成 SVG 图

这是你的仓库?认领条目

回到顶部