WeKan Markdown 渲染机制详解:GFM 语法支持、markdown-it 配置与安全净化管线

WeKan Markdown 渲染机制详解:GFM 语法支持、markdown-it 配置与安全净化管线 WeKan Markdown 渲染机制详解GFM 语法支持、markdown-it 配置与安全净化管线【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekanWeKan当前仓库版本为 v11.72.0见 package.json在卡片名称、看板名称、卡片描述与列表/任务清单等多处文本输入位置支持 Markdown 渲染。本文基于仓库内 docs/Features/Editor/Markdown/Markdown.md 的原始说明深入 packages/markdown 包的实际源码完整梳理 WeKan 从 GFM 语法输入、markdown-it 解析、插件扩展到 DOMPurify 安全净化与异常兜底的全链路渲染机制帮助开发者既会用这些 Markdown 特性也能理解其在源码层的实现与限制。一、Markdown 在 WeKan 中可用在哪里原始文档 docs/Features/Editor/Markdown/Markdown.md 明确列出了当前支持 Markdown 的位置卡片名称Card name看板名称Board name——仅在“看板视图board view”中生效卡片描述Card description、列表list与任务清单task list。这一特性在早期 WeKan 中是逐步扩展的仓库文档中曾将“把 Markdown 扩展到 WeKan 更多位置”记录为独立的开发议题原文引用的 issue #2334当前版本中上述文本输入框均走同一个 Blaze 模板辅助函数markdown因此行为一致。二、从 marked 到 markdown-it 的版本演进文档首行的 UPDATE 指出WeKan 现在使用markdown-it以及markdown-it-emoji插件。结合 docs/Features/Editor/Emoji.md 的说明可以还原演进脉络早期 WeKan 使用 markedmarked.js作为 GFM 解析器其 Meteor 移植包即仓库内的 packages/markdownpackages/markdown/README.md 中仍保留 marked 的历史致谢说明该包最初正是基于 marked.js 构建文档还提到该包曾是 GitHub 上 perak/markdown 的移植分支用于把 GFM 移植到 Meteor 环境WeKan v4.29起Markdown 渲染从 marked 切换为 markdown-it并同时引入 markdown-it-emoji 插件原始文档特别提示若要使用 GFM 任务清单checklist等 v0.28 语法特性需要Wekan v2.61。当前仓库远晚于此版本该特性已可用实现见下文第四节。三、编号文本的正确写法转义点号与自动编号这是原文档中最具实战价值的细节。当你在列表标题等位置想输入字面的编号文本时直接写3. Something会被 Markdown 解析器当作有序列表项处理。正确做法是用反斜杠转义点号3\. Something这样才会按字面显示为 “3. Something”。其背后原理来自 Markdown 的**自动编号autonumbering**机制docs/Features/Editor/Numbered-text.md 对此有完整解释即使所有条目都写成1.渲染时也会按顺序自动编号为 1、2、31. Something nice 1. Something cool 1. Something great渲染结果为1. Something nice 2. Something cool 3. Something great这一设计的价值在于你可以随意剪切/粘贴重排列表项而无需手动修改序号。例如重排为1. Something great 1. Something nice 1. Something cool渲染后会自动显示为 1. Something great / 2. Something nice / 3. Something cool。因此当“字面的 3.” 与“有序列表语法”冲突时用3\.转义是标准解法。四、wekan-markdown 包的 markdown-it 实例配置渲染核心位于 packages/markdown/src/template-integration.js。包声明见 packages/markdown/package.js其中Package.describe明确写着 “GitHub flavored markdown parser for Meteor based on markdown-it”版本 1.0.9包名wekan-markdown。markdown-it 实例的初始化参数template-integration.js 第 12–17 行为const Markdown new MarkdownIt({ html: true, linkify: true, typographer: true, breaks: true, });各参数含义html: true允许原文中的内联 HTML 通过 markdown-it 进入渲染流程后续会被 DOMPurify 净化见第五节因此不会造成 XSSlinkify: true把裸 URL 自动识别为链接这也是 WeKan 卡片 URL 自动关联见第六节的前提typographer: true启用排版替换如引号、省略号的规范化breaks: true单行换行即渲染为br更符合看板卡片这种短文本输入场景而非标准 Markdown 需要两个空格换行的行为。值得注意的是html: true并不意味着 HTML 能任意执行渲染管线末端有强制的 DOMPurify 白名单净化二者配合才是 WeKan 的真实安全模型。五、插件扩展Emoji、任务清单、LaTeX 与自定义 URL 方案5.1 markdown-it-emoji 表情插件markdown-it-emoji的full数据表被整体注册template-integration.js 第 269–272 行const emojiPlugin markdownItEmoji.full || markdownItEmoji.default || markdownItEmoji; if (emojiPlugin) { Markdown.use(emojiPlugin); }效果与 docs/Features/Editor/Emoji.md 一致在卡片名称、描述等位置输入:rainbow: :thumbsup: :100:即可渲染对应表情。5.2 GFM 任务清单复选框issue #2419 的修复原生 markdown-it 并不输出任务清单的input元素早期版本中- [ ] Task会被渲染成字面文本input disable typecheckbox/。仓库通过一个自研 core 规则修复template-integration.js 第 292–316 行规则名为task-lists挂在md.core.ruler末尾inline 解析完成之后执行只检查每个列表项的第一个文本 token 是否以[ ]或[x]开头正则^\[([ xX])\]\s匹配后把该前缀替换为一个真实的input typecheckbox disableddisabledtoken已勾选时追加checkedchecked。源码注释明确指出该复选框是禁用的、不可交互的点选回填需要把 DOM 节点映射回 Markdown 源文本的精确偏移量再走卡片更新 API 保存属于更大的独立功能不在此渲染修复范围内。该行为有对应测试 tests/markdownTaskListCheckbox.test.cjs 覆盖。净化端则在 packages/markdown/src/secureDOMPurify.js 中专门把input加入白名单并用 hook 把它锁死为“只能是 disabled checkbox、不能带 name/value/form 属性”的形态。5.3 LaTeX 数学公式Temml markdown-it-math卡片文本支持$...$行内与$$...$$块级数学公式由markdown-it-math的no-default-renderer入口点加载并自行调用 Temml 渲染为原生 MathMLtemplate-integration.js 第 318–339 行const renderMath (src, displayMode) { try { return temml.renderToString(src, { throwOnError: false, errorColor: #cc0000, displayMode }); } catch (e) { // Never let one malformed formula break the whole markdown render. return src; } };要点MathML 由浏览器原生显示、无需客户端渲染引擎一条错误公式只会显示为红色文本errorColor: #cc0000或回退为源码不会打断整段渲染输出用的 MathML 标签在 secureDOMPurify.js 第 7–25 行 中被显式白名单化且刻意排除了可夹带 HTML/SVG 的annotation-xml。详见 docs/Features/Editor/LaTeX.md。5.4 自定义 URL 方案与 linkify 崩溃修复issue #6588WeKan 额外向 linkify 注册了一批 URL 方案template-integration.js 第 185–267 行var urlschemes [ aodroplink, thunderlink, cbthunderlink, onenote, file, abasurl, conisio, mailspring ];源码注释记录了一个典型事故卡片描述中含file://链接会导致整张卡片无法打开原因是 linkify-it 6 移除了“字符串别名”写法linkify.add(scheme :, http:)展开后得到的对象没有validate方法linkify 校验阶段直接抛TypeError并使 Blaze 视图挂载失败。修复方式是每个方案自带validatevalidateSchemeTail匹配可选//开头、到空白或括号为止的 URL 尾部并剔除结尾标点。需要强调源码中的安全边界这些方案目前只让 markdown-it识别它们最终是否可点击由两道过滤器决定——markdown-it 自身的validateLink拒绝file:/javascript:/vbscript:/data:DOMPurify 仅允许http/https/ftp/ftps/mailto/tel/callto/cid/xmpp的 href所以这些链接最终渲染为普通文本。该行为由 tests/markdownCustomUrlSchemes.test.cjs 以真实渲染验证。5.5 SVG DoS 防护规则另有一条自研 core 规则svg-dos-protectiontemplate-integration.js 第 341–487 行检测 image token 中data:image/svg...或以.svg结尾的 src以及 HTML token 中的svg、xlink:href、use、defs等危险内容将其替换为红底警告段落 “⚠️ Blocked potentially malicious SVG image/content for security reasons”。这是针对 SVG 内嵌脚本/大量重复元素类 DoS 的渲染期拦截。六、自动链接增强外部问题跟踪器与 WeKan 卡片 URL在正式调用Markdown.render之前Blaze 辅助函数还会做两次纯文本预处理template-integration.js 第 540–555 行外部 issue 引用自动链接管理员配置externalLinkPatternPrefix与externalLinkPatternUrlURL 模板必须含{number}占位符否则视为关闭后文本中裸写的#1234形式的 token 会被替换为指向外部跟踪器的 Markdown 链接已处于](链接内部的 token 会被跳过isInsideExistingLinkForExternalLink判定。纯算法版本在 models/lib/externalLinkAutolink.js 中并有单测包内为镜像副本源码注释要求两者保持同步因为 Meteor 包不能 import 应用代码WeKan 卡片 URL 自动重命名issue #2453粘贴的形如/b/boardId/listId/cardId的卡片链接会通过Markdown.resolveCardTitle(cardId)由 client/components/main/editor.js 在启动时注入内部走 ReactiveCache解析为当前卡片标题渲染成卡片标题解析失败卡片已删、当前用户无权查看、桥接未接线时保留裸 URL 不动。标题中的反斜杠与]会先转义避免截断链接标签对应 models/lib/cardUrlAutolink.js 中注释的 CodeQL 告警。七、安全净化管线DOMPurify 白名单与多种防护渲染产出的 HTML 一律经过DOMPurify.sanitize(renderedMarkdown, getSecureDOMPurifyConfig())净化template-integration.js 第 556 行配置集中定义在 packages/markdown/src/secureDOMPurify.jsALLOWED_TAGSa、p、br、strong、em、u、s、del、strike、h1–h6、ul/ol/li、blockquote、pre/code、img、表格系列标签、hr、div、span、input以及 Temml 输出的 MathML 标签集FORBID_TAGSscript、style、iframe、object、embed、applet、全套 SVG 标签svg/defs/use/g/symbol/marker/pattern/...、form/textarea/select/button/label等FORBID_ATTR全部on*事件属性以及style、class、id、data-*、aria-*等URI 白名单ALLOWED_URI_REGEXP仅放行http/https/mailto/tel/callto/cid/xmpp等安全协议ALLOW_UNKNOWN_PROTOCOLS: falseHOOKS 钩子uponSanitizeElement再次拦截危险元素、SVG data URI 的img含 base64 解码后检查是否内嵌script/javascript:并把input锁死为纯 checkboxuponSanitizeAttribute阻止style/class/id/data-*属性、且href只允许出现在a上。围绕“信息隐藏攻击”还有三道独立防线全部体现在markdownBlaze 辅助函数template-integration.js 第 501–567 行中隐藏 Markdown 链接检测若原文含[]即“有链接但描述为空”的写法整个内容不渲染 Markdown而是经escapeHtmlSource全量转义后以红色背景pre显示源码并标注 “Warning! Hidden markdown link description!”HTML 注释显形正常渲染时也会把!--与--替换为红色带警告标题的实体文本防止用注释夹带不可见内容管理员级“代码全部按纯文本显示”Markdown.alwaysShowCodeAsText是一个 ReactiveVar与后台 Admin Panel → Features/Security 的alwaysShowCodeAsText设置联动包无法 import 应用设置故由 editor.js 桥接同步开启后所有 Markdown/HTML 一律转义为可见纯文本隐藏链接、注释、脚本源码全部可见、不可点击、不可执行。此外整个渲染过程包在try/catch中任何插件、病态公式或 markdown-it 升级导致的异常都不会让卡片详情面板挂载失败而是降级为 “This text could not be formatted, so it is shown as it was written” 的纯文本pre输出源码注释明确引用了 issue #6588 的教训一个畸形链接曾让整张卡片无法打开。八、使用方式Blaze 模板中的{{#markdown}}块packages/markdown/README.md 给出了对模板开发者的官方用法在任意模板中开一个 markdown 块即可{{#markdown}} ...markdown text here... {{/markdown}}底层实现是Blaze.Template.registerHelper(markdown, ...)注册的模板辅助函数template-integration.js 第 501 行它把模板块内的文本转为字符串依次执行“隐藏链接检测 → 外部 issue 自动链接 → 卡片 URL 自动重命名 → markdown-it 渲染 → 注释显形 → DOMPurify 净化”后以HTML.Raw注入。这也解释了为什么 WeKan 所有支持 Markdown 的输入框卡片名、描述、列表标题、任务清单行为完全一致——它们共用这一个渲染入口。九、相关文档与验证入口围绕本主题仓库内可直接延伸阅读的材料docs/Features/Editor/Markdown/Markdown.md本文的核心源文档含编号文本转义与可用位置清单docs/Features/Editor/Numbered-text.md自动编号机制完整说明docs/Features/Editor/Emoji.mdmarked → markdown-it 切换背景与表情语法docs/Features/Editor/LaTeX.md、docs/Features/Editor/Mermaid-Diagram.md公式与图表扩展源码packages/markdown/package.js、packages/markdown/src/template-integration.js、packages/markdown/src/secureDOMPurify.js测试tests/markdownTaskListCheckbox.test.cjs、tests/markdownCustomUrlSchemes.test.cjs、tests/exportMarkdown.test.cjs导出侧对应实现见 models/lib/exportMarkdown.js。综上WeKan 的 Markdown 能力可以概括为一条链路GFM 语法含转义与自动编号等细节约定→ markdown-ithtml/linkify/typographer/breaks 全开→ 自研 core 规则任务清单、SVG 防护与插件emoji、MathML 公式、自定义 URL 方案→ 渲染前文本级自动链接增强 → DOMPurify 白名单净化 → 隐藏信息防护与异常兜底。理解这条链路即可准确预判任意卡片文本在 WeKan 中的最终呈现与安全边界。【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考