- 添加 convertMermaidCodeblocks() 函数,在代码高亮前拦截 mermaid 代码块 - 支持标准 Markdown 代码块 (\\\mermaid) 渲染 - 更新 detectMermaidBlocks() 添加 mermaid-from-codeblock 选择器 - 更新 extractMermaidCode() 支持新容器类型 - 创建测试文件 test-codeblock-magic.html - 更新用户文档、开发者文档和 FAQ - 完全绕过代码高亮和 WordPress 格式化 - 支持 PJAX 页面切换 - 特殊字符和换行符正确保留
19 KiB
Mermaid 代码块魔改支持 - 设计文档
1. 设计概述
1.1 设计目标
实现标准 Markdown 代码块 (```mermaid) 的 Mermaid 图表渲染,通过在代码高亮之前拦截并转换代码块,完全绕过 WordPress 和代码高亮的干扰。
1.2 核心设计理念
提前拦截,转换容器:在代码高亮处理之前,将 <pre><code class="language-mermaid"> 转换为 <div class="mermaid-from-codeblock">,使其不被代码高亮处理,同时能被 Mermaid 渲染系统识别。
1.3 设计参考
参考主题中数学公式的实现方式:
- 数学公式使用特殊分隔符(
$...$),不会被代码高亮处理 - Mermaid 代码块通过提前转换,达到类似效果
- 两者都在 PJAX 加载后重新处理
1.4 技术栈
- JavaScript:原生 JavaScript + jQuery(主题现有技术栈)
- Mermaid.js:主题已集成的图表渲染库
- 执行时机:代码高亮之前(
highlightJsRender()函数开始处)
2. 架构设计
2.1 整体流程
flowchart TD
A[页面加载/PJAX切换] --> B[highlightJsRender 调用]
B --> C[convertMermaidCodeblocks 执行]
C --> D[查找 mermaid 代码块]
D --> E{找到代码块?}
E -->|是| F[提取纯文本代码]
E -->|否| G[继续代码高亮]
F --> H[创建 mermaid-from-codeblock 容器]
H --> I[替换原始代码块]
I --> J[标记已处理]
J --> G
G --> K[代码高亮处理其他代码块]
K --> L[detectMermaidBlocks 检测]
L --> M[提取 Mermaid 代码]
M --> N[mermaid.init 渲染]
2.2 模块设计
模块 1:代码块转换器 (convertMermaidCodeblocks)
职责:在代码高亮前拦截并转换 mermaid 代码块
输入:DOM 树(包含未处理的代码块) 输出:转换后的 DOM 树(mermaid 代码块已替换为容器)
核心逻辑:
- 使用多个选择器查找代码块
- 提取纯文本代码
- 创建新容器
- 替换原始元素
模块 2:代码提取器 (extractMermaidCode)
职责:从不同格式的容器中提取 Mermaid 代码
输入:DOM 元素(可能是 div、pre、code) 输出:纯文本 Mermaid 代码
支持格式:
<div class="mermaid-from-codeblock">(新增)<div class="mermaid-shortcode"><div class="mermaid"><pre><code class="language-mermaid">(降级)
模块 3:Mermaid 检测器 (detectMermaidBlocks)
职责:检测页面中所有需要渲染的 Mermaid 容器
输入:DOM 树 输出:需要渲染的元素列表
检测优先级:
div.mermaid-shortcode(Shortcode 格式)div.mermaid-from-codeblock(代码块魔改格式)div.mermaid(标准格式)pre code.language-mermaid(降级格式)
2.3 数据流设计
原始 HTML:
<pre><code class="language-mermaid">
flowchart TD
A --> B
</code></pre>
↓ convertMermaidCodeblocks()
转换后 HTML:
<div class="mermaid-from-codeblock" data-processed="true">
flowchart TD
A --> B
</div>
↓ detectMermaidBlocks()
检测到的代码:
"flowchart TD\n A --> B"
↓ mermaid.init()
渲染后 HTML:
<div class="mermaid-from-codeblock" data-processed="true">
<svg>...</svg>
</div>
3. 详细设计
3.1 代码块转换函数
函数签名
function convertMermaidCodeblocks()
实现位置
argontheme.js 第 3942 行之前(highlightJsRender() 函数开始处)
选择器设计
const selectors = [
'pre > code.language-mermaid', // 标准 Markdown 格式(最常见)
'pre > code.mermaid', // 简化格式
'code.language-mermaid', // 无 pre 包裹
'pre[data-lang="mermaid"]' // 自定义属性格式
];
设计理由:
- 支持多种插件生成的 HTML 结构
- 优先匹配最常见的格式
- 提供降级支持
重复处理防护
if (element.dataset.mermaidProcessed) {
return; // 跳过已处理的元素
}
设计理由:
- 避免 PJAX 切换时重复处理
- 防止多次调用导致的错误
- 使用 data 属性标记状态
代码提取逻辑
let code = element.textContent.trim();
设计理由:
textContent获取纯文本,避免 HTML 实体trim()移除前后空白- 不进行任何字符转换,保持原始内容
容器创建逻辑
const container = document.createElement('div');
container.className = 'mermaid-from-codeblock';
container.textContent = code;
container.dataset.processed = 'true';
设计理由:
- 使用
textContent而非innerHTML,避免 XSS - 添加特定类名,便于识别来源
- 标记已处理状态
元素替换逻辑
const targetElement = element.closest('pre') || element;
targetElement.parentNode.replaceChild(container, targetElement);
设计理由:
- 优先替换整个
<pre>元素 - 如果没有
<pre>包裹,替换<code>元素 - 保留原始位置和上下文
3.2 集成点设计
集成点 1:highlightJsRender() 函数
位置:argontheme.js 第 3942 行
修改前:
function highlightJsRender(){
if (typeof(hljs) == "undefined"){
return;
}
// ... 代码高亮逻辑
}
修改后:
function highlightJsRender(){
// 在代码高亮之前,先处理 Mermaid 代码块
convertMermaidCodeblocks();
if (typeof(hljs) == "undefined"){
return;
}
// ... 代码高亮逻辑
}
设计理由:
- 在代码高亮之前执行,确保 mermaid 代码块不被处理
- 不影响其他代码块的高亮
- 执行顺序:转换 → 高亮 → 渲染
集成点 2:detectMermaidBlocks() 函数
位置:argontheme.js 第 4430 行
修改前:
const selectors = [
'div.mermaid-shortcode',
'div.mermaid',
'pre code.language-mermaid',
// ...
];
修改后:
const selectors = [
'div.mermaid-shortcode', // Shortcode 格式
'div.mermaid-from-codeblock', // 代码块魔改格式(新增)
'div.mermaid', // 标准格式
'pre code.language-mermaid', // Markdown 格式(降级)
'pre[data-lang="mermaid"]', // 自定义属性格式
'code.mermaid' // 简化格式
];
设计理由:
- 添加新的容器类型到检测列表
- 优先级高于标准
mermaid类 - 保留降级选择器,确保兼容性
集成点 3:extractMermaidCode() 函数
位置:argontheme.js 第 4650 行
修改前:
// 处理 Shortcode 格式
if (element.classList.contains('mermaid-shortcode')) {
code = element.textContent;
this.logDebug('从 Shortcode 格式提取代码');
}
修改后:
// 处理 Shortcode 格式
if (element.classList.contains('mermaid-shortcode')) {
code = element.textContent;
this.logDebug('从 Shortcode 格式提取代码');
}
// 处理代码块魔改格式
else if (element.classList.contains('mermaid-from-codeblock')) {
code = element.textContent;
this.logDebug('从代码块魔改格式提取代码');
}
设计理由:
- 与 Shortcode 格式使用相同的提取方式
- 添加调试日志,便于追踪
- 保持代码一致性
3.3 PJAX 兼容设计
执行时机
PJAX 加载完成后的回调链(argontheme.js 第 2862-2890 行):
$(document).on('pjax:complete', function() {
// ... 其他初始化
try { highlightJsRender(); } catch (err) { ... } // 包含代码块转换
// ... 其他初始化
});
设计理由:
highlightJsRender()已在 PJAX 回调中调用- 代码块转换自动在每次 PJAX 切换后执行
- 无需额外修改 PJAX 逻辑
重复处理防护
使用 data-processed 属性标记已处理的元素:
if (element.dataset.mermaidProcessed) {
return;
}
// ... 处理逻辑
element.dataset.mermaidProcessed = 'true';
设计理由:
- 避免同一元素被多次转换
- 支持 PJAX 页面切换
- 轻量级标记,不影响性能
3.4 错误处理设计
空代码检查
let code = element.textContent.trim();
if (!code) {
return; // 跳过空代码块
}
设计理由:
- 避免创建空容器
- 减少不必要的 DOM 操作
- 提高性能
Try-Catch 包裹
try {
convertMermaidCodeblocks();
} catch (err) {
console.error('Mermaid 代码块转换失败:', err);
}
设计理由:
- 捕获异常,不中断其他代码块的处理
- 记录错误日志,便于调试
- 提供降级方案(代码块仍可通过降级选择器检测)
降级支持
如果代码块转换失败,仍可通过降级选择器检测:
'pre code.language-mermaid' // 降级选择器
设计理由:
- 确保即使转换失败,仍能渲染
- 提供多层保障
- 增强系统健壮性
4. 接口设计
4.1 公共函数
convertMermaidCodeblocks()
/**
* 在代码高亮之前转换 Mermaid 代码块
* 将 <pre><code class="language-mermaid"> 转换为 <div class="mermaid-from-codeblock">
*
* @returns {void}
*/
function convertMermaidCodeblocks() {
// 实现逻辑
}
4.2 数据结构
容器元素结构
<div class="mermaid-from-codeblock" data-processed="true">
flowchart TD
A --> B
</div>
属性说明:
class="mermaid-from-codeblock":标识来源于代码块data-processed="true":标记已处理- 内容:纯文本 Mermaid 代码
4.3 选择器优先级
| 优先级 | 选择器 | 用途 |
|---|---|---|
| 1 | pre > code.language-mermaid |
标准 Markdown 格式 |
| 2 | pre > code.mermaid |
简化格式 |
| 3 | code.language-mermaid |
无 pre 包裹 |
| 4 | pre[data-lang="mermaid"] |
自定义属性格式 |
5. 性能设计
5.1 性能目标
- 单个代码块处理时间 < 10ms
- 不影响页面加载速度
- 不增加额外的 HTTP 请求
5.2 性能优化策略
优化 1:使用原生 JavaScript
document.querySelectorAll(selector) // 而非 $(selector)
理由:原生方法性能更好,减少 jQuery 开销
优化 2:提前返回
if (element.dataset.mermaidProcessed) {
return; // 提前返回,避免不必要的处理
}
理由:减少重复处理,提高效率
优化 3:批量处理
selectors.forEach(selector => {
document.querySelectorAll(selector).forEach(element => {
// 处理逻辑
});
});
理由:一次性查找所有元素,减少 DOM 查询次数
优化 4:最小化 DOM 操作
const container = document.createElement('div');
container.className = 'mermaid-from-codeblock';
container.textContent = code;
container.dataset.processed = 'true';
targetElement.parentNode.replaceChild(container, targetElement);
理由:一次性创建和替换,减少重排和重绘
5.3 性能监控
调试日志
this.logDebug('处理了 ' + count + ' 个 Mermaid 代码块');
this.logDebug('代码内容(前100字符): ' + code.substring(0, 100));
理由:便于追踪性能问题,不影响生产环境
6. 安全设计
6.1 XSS 防护
使用 textContent 而非 innerHTML
container.textContent = code; // 安全
// container.innerHTML = code; // 不安全
理由:textContent 会自动转义 HTML,防止 XSS 攻击
代码来源验证
let code = element.textContent.trim();
if (!code) {
return; // 拒绝空代码
}
理由:避免处理恶意或无效的代码块
6.2 DOM 操作安全
安全的元素替换
const targetElement = element.closest('pre') || element;
if (targetElement.parentNode) {
targetElement.parentNode.replaceChild(container, targetElement);
}
理由:检查父节点存在,避免 null 引用错误
7. 测试设计
7.1 单元测试
测试用例 1:标准代码块转换
// 输入
<pre><code class="language-mermaid">
flowchart TD
A --> B
</code></pre>
// 预期输出
<div class="mermaid-from-codeblock" data-processed="true">
flowchart TD
A --> B
</div>
测试用例 2:空代码块处理
// 输入
<pre><code class="language-mermaid"></code></pre>
// 预期输出
<pre><code class="language-mermaid"></code></pre> // 不转换
测试用例 3:重复处理防护
// 第一次处理
convertMermaidCodeblocks(); // 转换成功
// 第二次处理
convertMermaidCodeblocks(); // 跳过已处理的元素
测试用例 4:特殊字符保留
// 输入
<pre><code class="language-mermaid">
A --> B
C -- text --> D
</code></pre>
// 预期输出
代码中的 --> 和 -- 保持不变
7.2 集成测试
测试场景 1:与代码高亮集成
- 页面包含 mermaid 代码块和其他代码块
- 调用
highlightJsRender() - 验证 mermaid 代码块被转换,其他代码块被高亮
测试场景 2:与 Mermaid 渲染集成
- 页面包含转换后的容器
- 调用
detectMermaidBlocks() - 验证容器被检测到
- 调用
mermaid.init() - 验证图表正确渲染
测试场景 3:PJAX 兼容性
- 初始页面加载,代码块正确转换和渲染
- PJAX 切换到新页面
- 验证新页面的代码块正确转换和渲染
- 验证已转换的代码块不被重复处理
7.3 浏览器测试
测试矩阵
| 浏览器 | 版本 | 测试项 |
|---|---|---|
| Chrome | 最新版 | 全部功能 |
| Firefox | 最新版 | 全部功能 |
| Safari | 最新版 | 全部功能 |
| Edge | 最新版 | 全部功能 |
测试项
- 代码块转换
- 图表渲染
- PJAX 切换
- 性能表现
7.4 测试文件
创建 tests/test-codeblock-magic.html:
<!DOCTYPE html>
<html>
<head>
<title>Mermaid 代码块魔改测试</title>
</head>
<body>
<!-- 测试用例 1:标准格式 -->
<pre><code class="language-mermaid">
flowchart TD
A --> B
</code></pre>
<!-- 测试用例 2:多个代码块 -->
<pre><code class="language-mermaid">
graph LR
C --> D
</code></pre>
<!-- 测试用例 3:特殊字符 -->
<pre><code class="language-mermaid">
flowchart TD
A -- text --> B
C ==> D
</code></pre>
<!-- 测试用例 4:空代码块 -->
<pre><code class="language-mermaid"></code></pre>
</body>
</html>
8. 部署设计
8.1 部署步骤
步骤 1:修改 argontheme.js
- 在
highlightJsRender()函数开始处添加convertMermaidCodeblocks()调用 - 实现
convertMermaidCodeblocks()函数 - 修改
detectMermaidBlocks()函数,添加新选择器 - 修改
extractMermaidCode()函数,支持新容器类型
步骤 2:测试验证
- 创建测试文件
tests/test-codeblock-magic.html - 在本地环境测试所有用例
- 验证 PJAX 兼容性
- 检查浏览器控制台无错误
步骤 3:文档更新
- 更新
docs/mermaid-usage-guide.md - 更新
docs/mermaid-developer-guide.md - 更新
docs/mermaid-faq.md
步骤 4:发布
- 提交代码到 Git
- 更新版本号
- 发布更新
8.2 回滚方案
如果出现问题,可以快速回滚:
- 移除
convertMermaidCodeblocks()调用 - 移除新添加的选择器
- 恢复原始代码
降级方案:
- 用户仍可使用 Shortcode 格式
- 用户仍可使用容器语法
- 不影响现有功能
8.3 兼容性保证
向后兼容
- 不影响现有的 Shortcode 格式
- 不影响现有的容器语法
- 不影响其他代码块的高亮
向前兼容
- 预留扩展接口
- 支持未来的新格式
- 易于维护和升级
9. 监控与维护
9.1 日志设计
调试日志
this.logDebug('开始转换 Mermaid 代码块');
this.logDebug('找到 ' + elements.length + ' 个代码块');
this.logDebug('代码内容: ' + code.substring(0, 100));
this.logDebug('转换完成');
错误日志
console.error('Mermaid 代码块转换失败:', err);
console.error('元素:', element);
console.error('代码:', code);
9.2 性能监控
性能指标
- 代码块转换时间
- 页面加载时间
- 内存使用情况
监控方法
const startTime = performance.now();
convertMermaidCodeblocks();
const endTime = performance.now();
console.log('转换耗时:', endTime - startTime, 'ms');
9.3 维护计划
定期检查
- 每月检查浏览器兼容性
- 每季度检查性能表现
- 每半年检查代码质量
更新策略
- 跟进 Mermaid.js 版本更新
- 跟进 WordPress 版本更新
- 跟进浏览器标准更新
10. 风险与缓解
10.1 技术风险
风险 1:与其他插件冲突
影响:中等 概率:低 缓解措施:
- 使用唯一的类名
mermaid-from-codeblock - 添加命名空间,避免冲突
- 提供配置选项,允许禁用
风险 2:性能问题
影响:低 概率:低 缓解措施:
- 优化选择器,减少 DOM 查询
- 使用缓存,避免重复处理
- 添加性能监控
风险 3:浏览器兼容性
影响:中等 概率:低 缓解措施:
- 使用标准 API
- 添加 polyfill
- 提供降级方案
10.2 用户体验风险
风险 1:误转换其他代码块
影响:高 概率:极低 缓解措施:
- 使用精确的选择器
- 添加类名检查
- 提供白名单机制
风险 2:特殊字符丢失
影响:高 概率:极低 缓解措施:
- 使用
textContent保留原始内容 - 不进行任何字符转换
- 添加测试用例验证
11. 未来扩展
11.1 短期扩展(1-2 周)
配置选项
添加主题设置选项:
- 是否启用代码块魔改
- 选择器优先级配置
- 调试模式开关
性能优化
- 添加缓存机制
- 批量处理优化
- 延迟加载支持
11.2 中期扩展(1-2 月)
编辑器支持
- 添加编辑器预览功能
- 支持实时渲染
- 提供语法提示
移动端优化
- 优化移动端显示
- 支持触摸交互
- 响应式布局
11.3 长期扩展(3-6 月)
多图表库支持
- 支持 PlantUML
- 支持 GraphViz
- 支持 D3.js
高级功能
- 图表编辑器
- 图表导出(PNG、SVG)
- 图表分享
12. 总结
12.1 设计优势
- 简单高效:只需添加一个函数,修改三个位置
- 兼容性好:不影响现有功能,支持多种格式
- 性能优秀:使用原生 JavaScript,优化 DOM 操作
- 易于维护:代码清晰,注释详细,易于理解
- 安全可靠:防止 XSS,处理异常,提供降级
12.2 关键决策
| 决策 | 理由 |
|---|---|
| 在代码高亮前拦截 | 避免代码高亮干扰 |
| 使用 textContent | 保留原始内容,防止 XSS |
| 创建新容器类型 | 便于识别来源,支持多种格式 |
| 添加重复处理防护 | 支持 PJAX,避免重复转换 |
| 提供降级支持 | 确保即使转换失败仍能渲染 |
12.3 实施建议
- 分步实施:先实现核心功能,再添加优化
- 充分测试:创建完整的测试用例,覆盖所有场景
- 文档完善:更新用户文档和开发者文档
- 监控反馈:收集用户反馈,持续优化
12.4 成功标准
- ✅ 可以使用
```mermaid代码块编写图表 - ✅ 代码块不会被代码高亮处理
- ✅ 图表正确渲染
- ✅ 特殊字符不被转换
- ✅ 换行符正确保留
- ✅ PJAX 切换正常工作
- ✅ 性能无明显影响
- ✅ 兼容所有主流浏览器