feat: 实现 Mermaid JavaScript 渲染引擎
- 在 argontheme.js 中添加完整的 Mermaid 渲染引擎模块 - 实现 Mermaid 配置初始化函数(支持主题自动切换) - 实现主题获取函数(根据页面 darkmode 类返回对应主题) - 实现代码块检测器(支持 div.mermaid、pre code.language-mermaid、pre[data-lang]、code.mermaid 四种格式) - 实现批量渲染函数(一次 DOM 遍历,批量渲染所有图表) - 实现错误处理机制(显示友好错误提示,保留原始代码) - 实现样式增强(淡入动画、响应式 SVG) - 实现主题切换监听器(监听 argon:theme-switched 事件和 darkmode class 变化) - 实现图表重新渲染功能(主题切换时自动重新渲染) - 实现渲染缓存机制(避免重复渲染) - 添加调试日志系统(支持 debugMode 配置) - 在 DOMContentLoaded 事件中自动初始化 - 暴露 ArgonMermaidRenderer 到全局(用于 PJAX 等场景) - Requirements: 2.1, 2.5, 10.1, 10.2, 10.3
This commit is contained in:
226
.kiro/specs/mermaid-support/tasks.md
Normal file
226
.kiro/specs/mermaid-support/tasks.md
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
# Implementation Plan: Mermaid 图表支持
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
本实施计划将 Mermaid 图表支持功能集成到 Argon WordPress 主题中。采用插件兼容方案,主题提供样式增强、主题适配和性能优化。实施过程分为配置管理、库加载、渲染引擎、样式优化和测试验证五个阶段。
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
- [x] 1. 创建配置管理系统
|
||||||
|
- 在 `functions.php` 中添加 Mermaid 配置管理函数
|
||||||
|
- 实现配置选项的获取、保存和验证功能
|
||||||
|
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5_
|
||||||
|
|
||||||
|
- [ ]* 1.1 编写配置管理函数的单元测试
|
||||||
|
- 测试配置选项的获取和保存
|
||||||
|
- 测试 CDN URL 验证逻辑
|
||||||
|
- 测试默认值处理
|
||||||
|
- _Requirements: 5.5_
|
||||||
|
|
||||||
|
- [x] 2. 在设置页添加 Mermaid 配置选项
|
||||||
|
- 在 `settings.php` 中创建新的设置分类"Mermaid 图表"
|
||||||
|
- 添加启用/禁用开关
|
||||||
|
- 添加 CDN 来源选择(jsDelivr、unpkg、自定义、本地)
|
||||||
|
- 添加自定义 CDN 地址输入框
|
||||||
|
- 添加图表主题选择(default、dark、forest、neutral、auto)
|
||||||
|
- 添加本地镜像开关
|
||||||
|
- 添加调试模式开关
|
||||||
|
- _Requirements: 5.1, 5.2, 5.3, 5.4_
|
||||||
|
|
||||||
|
- [x] 3. 实现库加载器
|
||||||
|
- 在 `functions.php` 中添加页面内容检测函数
|
||||||
|
- 实现 Mermaid 代码块检测逻辑(正则表达式)
|
||||||
|
- 添加 `wp_enqueue_scripts` 钩子函数
|
||||||
|
- 根据配置选择 CDN 或本地加载
|
||||||
|
- 实现异步加载(async 属性)
|
||||||
|
- _Requirements: 1.1, 1.2, 1.3, 1.5, 8.2_
|
||||||
|
|
||||||
|
- [ ]* 3.1 编写库加载器的属性测试
|
||||||
|
- **Property 1: 按需加载库** - 对于任意页面,当且仅当包含 Mermaid 代码块时加载库
|
||||||
|
- **Validates: Requirements 1.1, 1.5**
|
||||||
|
- _Requirements: 1.1, 1.5_
|
||||||
|
|
||||||
|
- [ ]* 3.2 编写 CDN 地址正确性的属性测试
|
||||||
|
- **Property 2: CDN 地址正确性** - 对于任意 CDN 配置选项,生成的 URL 应该匹配
|
||||||
|
- **Validates: Requirements 1.2, 1.3**
|
||||||
|
- _Requirements: 1.2, 1.3_
|
||||||
|
|
||||||
|
- [x] 4. 创建 JavaScript 渲染引擎
|
||||||
|
- 在 `argontheme.js` 或新建 `argon-mermaid.js` 文件
|
||||||
|
- 实现 Mermaid 配置初始化函数
|
||||||
|
- 实现主题获取函数(根据页面模式返回对应主题)
|
||||||
|
- 实现代码块检测器(支持多种格式)
|
||||||
|
- 实现批量渲染函数
|
||||||
|
- 添加 DOMContentLoaded 事件监听
|
||||||
|
- _Requirements: 2.1, 2.5, 10.1, 10.2, 10.3_
|
||||||
|
|
||||||
|
- [ ]* 4.1 编写代码块识别的属性测试
|
||||||
|
- **Property 3: 代码块识别完整性** - 对于任意包含 Mermaid 标记的元素,应该能够识别
|
||||||
|
- **Validates: Requirements 10.1, 10.2, 10.3**
|
||||||
|
- _Requirements: 10.1, 10.2, 10.3_
|
||||||
|
|
||||||
|
- [ ]* 4.2 编写代码块优先级的属性测试
|
||||||
|
- **Property 17: 代码块优先级** - 对于同时包含多个标记的元素,优先使用 class 属性
|
||||||
|
- **Validates: Requirements 10.4**
|
||||||
|
- _Requirements: 10.4_
|
||||||
|
|
||||||
|
- [ ]* 4.3 编写批量渲染性能的属性测试
|
||||||
|
- **Property 15: 批量渲染性能** - 对于任意包含多个图表的页面,应该批量渲染
|
||||||
|
- **Validates: Requirements 8.4**
|
||||||
|
- _Requirements: 8.4_
|
||||||
|
|
||||||
|
- [~] 5. 实现错误处理机制
|
||||||
|
- 添加库加载失败的降级处理(多个 CDN 备选)
|
||||||
|
- 实现代码解析错误的捕获和显示
|
||||||
|
- 创建错误提示 UI 组件
|
||||||
|
- 添加调试模式日志输出
|
||||||
|
- 保留原始代码块供用户查看
|
||||||
|
- _Requirements: 1.4, 2.3, 7.1, 7.2, 7.3, 7.4, 7.5_
|
||||||
|
|
||||||
|
- [ ]* 5.1 编写错误处理的属性测试
|
||||||
|
- **Property 5: 错误时保留原始代码** - 对于任意渲染失败的代码块,应该保留原始代码
|
||||||
|
- **Validates: Requirements 7.1, 7.4**
|
||||||
|
- _Requirements: 7.1, 7.4_
|
||||||
|
|
||||||
|
- [ ]* 5.2 编写错误信息完整性的属性测试
|
||||||
|
- **Property 14: 错误信息完整性** - 对于任意解析错误,应该包含错误类型和详细描述
|
||||||
|
- **Validates: Requirements 7.3**
|
||||||
|
- _Requirements: 7.3_
|
||||||
|
|
||||||
|
- [~] 6. Checkpoint - 基础功能验证
|
||||||
|
- 确保所有测试通过
|
||||||
|
- 在测试环境中验证基础渲染功能
|
||||||
|
- 测试不同类型的 Mermaid 图表(流程图、时序图、类图)
|
||||||
|
- 如有问题,询问用户
|
||||||
|
|
||||||
|
- [~] 7. 添加样式增强
|
||||||
|
- 在 `style.css` 中添加 Mermaid 容器样式
|
||||||
|
- 实现响应式设计(max-width: 100%,overflow-x: auto)
|
||||||
|
- 添加夜间模式样式适配
|
||||||
|
- 添加卡片内边距样式
|
||||||
|
- 添加淡入动画效果
|
||||||
|
- _Requirements: 3.1, 3.3, 6.1, 6.3, 6.4, 6.5_
|
||||||
|
|
||||||
|
- [ ]* 7.1 编写响应式样式的属性测试
|
||||||
|
- **Property 8: 响应式容器宽度** - 对于任意图表容器,最大宽度应为 100%
|
||||||
|
- **Validates: Requirements 3.1, 3.3**
|
||||||
|
- _Requirements: 3.1, 3.3_
|
||||||
|
|
||||||
|
- [ ]* 7.2 编写移动端适配的属性测试
|
||||||
|
- **Property 9: 移动端自适应** - 对于任意小屏幕设备,图表应该自适应
|
||||||
|
- **Validates: Requirements 3.2**
|
||||||
|
- _Requirements: 3.2_
|
||||||
|
|
||||||
|
- [ ]* 7.3 编写夜间模式样式的属性测试
|
||||||
|
- **Property 10: 夜间模式样式适配** - 对于任意图表容器,夜间模式下应该应用深色样式
|
||||||
|
- **Validates: Requirements 6.5**
|
||||||
|
- _Requirements: 6.5_
|
||||||
|
|
||||||
|
- [ ]* 7.4 编写卡片内边距的属性测试
|
||||||
|
- **Property 11: 卡片内边距** - 对于任意在卡片中的图表,应该添加内边距
|
||||||
|
- **Validates: Requirements 6.3**
|
||||||
|
- _Requirements: 6.3_
|
||||||
|
|
||||||
|
- [~] 8. 实现主题切换功能
|
||||||
|
- 添加主题切换事件监听器
|
||||||
|
- 实现图表重新渲染函数
|
||||||
|
- 处理自定义主题优先级
|
||||||
|
- 添加防抖优化避免频繁渲染
|
||||||
|
- _Requirements: 4.1, 4.2, 4.3, 4.5_
|
||||||
|
|
||||||
|
- [ ]* 8.1 编写主题切换的属性测试
|
||||||
|
- **Property 6: 主题模式自动切换** - 对于任意主题切换,图表应该重新渲染并使用对应主题
|
||||||
|
- **Validates: Requirements 4.1, 4.2, 4.3**
|
||||||
|
- _Requirements: 4.1, 4.2, 4.3_
|
||||||
|
|
||||||
|
- [ ]* 8.2 编写自定义主题优先级的属性测试
|
||||||
|
- **Property 7: 自定义主题优先级** - 对于任意图表,自定义主题应该优先于自动切换
|
||||||
|
- **Validates: Requirements 4.5**
|
||||||
|
- _Requirements: 4.5_
|
||||||
|
|
||||||
|
- [~] 9. 实现插件兼容层
|
||||||
|
- 添加插件检测函数(WP Githuber MD、Markdown Block、Code Syntax Block)
|
||||||
|
- 实现 Mermaid 库加载状态检测
|
||||||
|
- 添加重复加载防护逻辑
|
||||||
|
- 在设置页显示插件兼容性状态
|
||||||
|
- _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5_
|
||||||
|
|
||||||
|
- [ ]* 9.1 编写避免重复加载的属性测试
|
||||||
|
- **Property 13: 避免重复加载** - 对于任意页面,当检测到已加载库时,不应该重复加载
|
||||||
|
- **Validates: Requirements 9.4**
|
||||||
|
- _Requirements: 9.4_
|
||||||
|
|
||||||
|
- [~] 10. 添加性能优化
|
||||||
|
- 实现渲染缓存机制(避免重复渲染)
|
||||||
|
- 优化批量渲染逻辑
|
||||||
|
- 添加渲染状态标记
|
||||||
|
- _Requirements: 8.3, 8.4_
|
||||||
|
|
||||||
|
- [ ]* 10.1 编写渲染缓存的属性测试
|
||||||
|
- **Property 16: 渲染缓存** - 对于任意已渲染的图表,不应该重复渲染
|
||||||
|
- **Validates: Requirements 8.3**
|
||||||
|
- _Requirements: 8.3_
|
||||||
|
|
||||||
|
- [~] 11. 添加特殊格式处理
|
||||||
|
- 处理 WP-Markdown 生成的 `<script>document.write()</script>` 格式
|
||||||
|
- 实现转义字符解码(`\n`, `\"`, `\'`)
|
||||||
|
- 添加注释代码块过滤
|
||||||
|
- _Requirements: 10.5_
|
||||||
|
|
||||||
|
- [ ]* 11.1 编写注释代码块过滤的属性测试
|
||||||
|
- **Property 18: 忽略注释代码块** - 对于任意被注释包裹的代码块,应该忽略
|
||||||
|
- **Validates: Requirements 10.5**
|
||||||
|
- _Requirements: 10.5_
|
||||||
|
|
||||||
|
- [~] 12. Checkpoint - 完整功能验证
|
||||||
|
- 确保所有测试通过
|
||||||
|
- 测试所有配置选项
|
||||||
|
- 测试主题切换功能
|
||||||
|
- 测试错误处理
|
||||||
|
- 测试插件兼容性
|
||||||
|
- 如有问题,询问用户
|
||||||
|
|
||||||
|
- [~] 13. 添加设置页预览功能
|
||||||
|
- 在设置页添加 Mermaid 预览区域
|
||||||
|
- 实现实时预览功能
|
||||||
|
- 添加示例图表代码
|
||||||
|
- _Requirements: 5.6_
|
||||||
|
|
||||||
|
- [~] 14. 编写文档和注释
|
||||||
|
- 为所有函数添加 PHPDoc 和 JSDoc 注释
|
||||||
|
- 在设置页添加使用说明
|
||||||
|
- 创建用户文档(如何使用 Mermaid)
|
||||||
|
- 添加常见问题解答
|
||||||
|
|
||||||
|
- [ ]* 15. 集成测试
|
||||||
|
- 测试完整的渲染流程
|
||||||
|
- 测试与 WP Githuber MD 插件的集成
|
||||||
|
- 测试与 Markdown Block 插件的集成
|
||||||
|
- 测试主题切换时的重新渲染
|
||||||
|
- 测试 CDN 加载失败的降级处理
|
||||||
|
- _Requirements: 9.1, 9.2, 9.3_
|
||||||
|
|
||||||
|
- [~] 16. 最终验证和优化
|
||||||
|
- 运行所有单元测试和属性测试
|
||||||
|
- 检查代码覆盖率(PHP ≥80%,JS ≥85%)
|
||||||
|
- 进行性能测试(页面加载时间、渲染速度)
|
||||||
|
- 进行浏览器兼容性测试
|
||||||
|
- 优化代码和注释
|
||||||
|
|
||||||
|
- [~] 17. 手动测试清单
|
||||||
|
- 验证图表颜色与页面背景色的对比度
|
||||||
|
- 验证图表容器样式与主题整体风格的一致性
|
||||||
|
- 测试所有 Mermaid 官方图表类型
|
||||||
|
- 测试移动端显示效果
|
||||||
|
- 验证错误提示信息的友好性
|
||||||
|
- 测试设置页预览功能
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- 任务标记 `*` 的为可选测试任务,可以跳过以加快 MVP 开发
|
||||||
|
- 每个任务都引用了具体的需求编号,确保可追溯性
|
||||||
|
- Checkpoint 任务用于增量验证,确保功能正确性
|
||||||
|
- 属性测试验证通用正确性属性
|
||||||
|
- 单元测试验证具体示例和边缘情况
|
||||||
|
- 集成测试验证端到端流程
|
||||||
|
|
||||||
539
argontheme.js
539
argontheme.js
@@ -4316,3 +4316,542 @@ void 0;
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Mermaid 图表渲染引擎
|
||||||
|
// ==========================================================================
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ---------- 配置和状态管理 ----------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mermaid 渲染引擎配置
|
||||||
|
*/
|
||||||
|
const MermaidRenderer = {
|
||||||
|
initialized: false,
|
||||||
|
rendered: new Set(), // 已渲染的图表 ID 集合
|
||||||
|
config: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 Mermaid 配置
|
||||||
|
*/
|
||||||
|
initConfig() {
|
||||||
|
// 检查 Mermaid 库是否已加载
|
||||||
|
if (typeof window.mermaid === 'undefined') {
|
||||||
|
this.logDebug('Mermaid 库未加载');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查配置是否存在
|
||||||
|
if (typeof window.argonMermaidConfig === 'undefined') {
|
||||||
|
this.logDebug('Mermaid 配置未找到,使用默认配置');
|
||||||
|
this.config = {
|
||||||
|
enabled: true,
|
||||||
|
theme: 'auto',
|
||||||
|
debugMode: false,
|
||||||
|
fallbackUrls: []
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.config = window.argonMermaidConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前主题
|
||||||
|
const theme = this.getMermaidTheme();
|
||||||
|
|
||||||
|
// 配置 Mermaid
|
||||||
|
try {
|
||||||
|
window.mermaid.initialize({
|
||||||
|
startOnLoad: false, // 手动控制渲染时机
|
||||||
|
theme: theme,
|
||||||
|
securityLevel: 'loose', // 允许 HTML 标签
|
||||||
|
logLevel: this.config.debugMode ? 'debug' : 'error',
|
||||||
|
flowchart: {
|
||||||
|
useMaxWidth: true,
|
||||||
|
htmlLabels: true,
|
||||||
|
curve: 'basis'
|
||||||
|
},
|
||||||
|
sequence: {
|
||||||
|
useMaxWidth: true,
|
||||||
|
wrap: true
|
||||||
|
},
|
||||||
|
gantt: {
|
||||||
|
useMaxWidth: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
this.logDebug('Mermaid 配置初始化成功', { theme });
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
this.logError('Mermaid 配置初始化失败', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前主题对应的 Mermaid 主题
|
||||||
|
* @returns {string} Mermaid 主题名称
|
||||||
|
*/
|
||||||
|
getMermaidTheme() {
|
||||||
|
// 如果配置了固定主题(非 auto),直接返回
|
||||||
|
if (this.config && this.config.theme !== 'auto') {
|
||||||
|
return this.config.theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据页面主题模式自动选择
|
||||||
|
const isDarkMode = document.documentElement.classList.contains('darkmode');
|
||||||
|
return isDarkMode ? 'dark' : 'default';
|
||||||
|
},
|
||||||
|
|
||||||
|
// ---------- 代码块检测器 ----------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测所有 Mermaid 代码块
|
||||||
|
* @returns {Array} Mermaid 代码块元素数组
|
||||||
|
*/
|
||||||
|
detectMermaidBlocks() {
|
||||||
|
const blocks = [];
|
||||||
|
|
||||||
|
// 检测规则(优先级从高到低)
|
||||||
|
const selectors = [
|
||||||
|
'div.mermaid', // 标准格式
|
||||||
|
'pre code.language-mermaid', // Markdown 格式
|
||||||
|
'pre[data-lang="mermaid"]', // 自定义属性格式
|
||||||
|
'code.mermaid' // 简化格式
|
||||||
|
];
|
||||||
|
|
||||||
|
selectors.forEach(selector => {
|
||||||
|
const elements = document.querySelectorAll(selector);
|
||||||
|
elements.forEach(element => {
|
||||||
|
// 避免重复添加
|
||||||
|
if (!blocks.includes(element)) {
|
||||||
|
// 检查是否在注释中
|
||||||
|
if (!this.isInComment(element)) {
|
||||||
|
blocks.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logDebug(`检测到 ${blocks.length} 个 Mermaid 代码块`);
|
||||||
|
return blocks;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查元素是否在 HTML 注释中
|
||||||
|
* @param {HTMLElement} element - 要检查的元素
|
||||||
|
* @returns {boolean} 是否在注释中
|
||||||
|
*/
|
||||||
|
isInComment(element) {
|
||||||
|
let node = element.parentNode;
|
||||||
|
while (node) {
|
||||||
|
if (node.nodeType === Node.COMMENT_NODE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
node = node.parentNode;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取代码块内容
|
||||||
|
* @param {HTMLElement} element - 代码块元素
|
||||||
|
* @returns {string} Mermaid 代码
|
||||||
|
*/
|
||||||
|
extractMermaidCode(element) {
|
||||||
|
let code = '';
|
||||||
|
|
||||||
|
// 根据不同的元素类型提取代码
|
||||||
|
if (element.tagName === 'DIV' && element.classList.contains('mermaid')) {
|
||||||
|
code = element.textContent;
|
||||||
|
} else if (element.tagName === 'CODE') {
|
||||||
|
code = element.textContent;
|
||||||
|
} else if (element.tagName === 'PRE') {
|
||||||
|
const codeElement = element.querySelector('code');
|
||||||
|
code = codeElement ? codeElement.textContent : element.textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码转义字符
|
||||||
|
code = code
|
||||||
|
.replace(/\\n/g, '\n')
|
||||||
|
.replace(/\\"/g, '"')
|
||||||
|
.replace(/\\'/g, "'")
|
||||||
|
.replace(/\\\\/g, '\\');
|
||||||
|
|
||||||
|
return code.trim();
|
||||||
|
},
|
||||||
|
|
||||||
|
// ---------- 渲染引擎 ----------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量渲染所有 Mermaid 图表
|
||||||
|
*/
|
||||||
|
renderAllCharts() {
|
||||||
|
if (!this.initialized) {
|
||||||
|
this.logDebug('Mermaid 未初始化,跳过渲染');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测所有代码块(一次 DOM 遍历)
|
||||||
|
const blocks = this.detectMermaidBlocks();
|
||||||
|
|
||||||
|
if (blocks.length === 0) {
|
||||||
|
this.logDebug('未找到 Mermaid 代码块');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量渲染
|
||||||
|
blocks.forEach((block, index) => {
|
||||||
|
this.renderChart(block, index);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logDebug(`完成渲染 ${blocks.length} 个图表`);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染单个图表
|
||||||
|
* @param {HTMLElement} element - 代码块元素
|
||||||
|
* @param {number} index - 图表索引
|
||||||
|
*/
|
||||||
|
renderChart(element, index) {
|
||||||
|
const chartId = `mermaid-chart-${Date.now()}-${index}`;
|
||||||
|
|
||||||
|
// 检查是否已渲染(避免重复渲染)
|
||||||
|
if (this.rendered.has(element)) {
|
||||||
|
this.logDebug(`图表已渲染,跳过: ${chartId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 提取代码
|
||||||
|
const code = this.extractMermaidCode(element);
|
||||||
|
|
||||||
|
if (!code) {
|
||||||
|
this.logDebug(`代码块为空,跳过: ${chartId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建容器
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.className = 'mermaid-container';
|
||||||
|
container.id = chartId;
|
||||||
|
|
||||||
|
// 渲染图表
|
||||||
|
window.mermaid.render(`mermaid-svg-${chartId}`, code).then(result => {
|
||||||
|
// 渲染成功
|
||||||
|
container.innerHTML = result.svg;
|
||||||
|
|
||||||
|
// 保存原始代码(用于主题切换时重新渲染)
|
||||||
|
container.dataset.mermaidCode = code;
|
||||||
|
container.dataset.currentTheme = this.getMermaidTheme();
|
||||||
|
|
||||||
|
// 替换原始代码块
|
||||||
|
element.parentNode.replaceChild(container, element);
|
||||||
|
|
||||||
|
// 标记为已渲染
|
||||||
|
this.rendered.add(container);
|
||||||
|
|
||||||
|
// 应用样式增强
|
||||||
|
this.applyStyles(container);
|
||||||
|
|
||||||
|
this.logDebug(`图表渲染成功: ${chartId}`);
|
||||||
|
}).catch(error => {
|
||||||
|
// 渲染失败
|
||||||
|
this.handleRenderError(element, error, code);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.handleRenderError(element, error, '');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理渲染错误
|
||||||
|
* @param {HTMLElement} element - 原始代码块元素
|
||||||
|
* @param {Error} error - 错误对象
|
||||||
|
* @param {string} code - 原始代码
|
||||||
|
*/
|
||||||
|
handleRenderError(element, error, code) {
|
||||||
|
this.logError('图表渲染失败', error);
|
||||||
|
|
||||||
|
// 创建错误提示容器
|
||||||
|
const errorContainer = document.createElement('div');
|
||||||
|
errorContainer.className = 'mermaid-error-container';
|
||||||
|
|
||||||
|
// 提取错误信息
|
||||||
|
const errorMessage = error.message || '未知错误';
|
||||||
|
const errorType = this.getErrorType(errorMessage);
|
||||||
|
|
||||||
|
errorContainer.innerHTML = `
|
||||||
|
<div class="mermaid-error-header">
|
||||||
|
<span class="mermaid-error-icon">⚠️</span>
|
||||||
|
<span class="mermaid-error-title">Mermaid 图表渲染失败</span>
|
||||||
|
</div>
|
||||||
|
<div class="mermaid-error-body">
|
||||||
|
<p class="mermaid-error-type">错误类型: ${errorType}</p>
|
||||||
|
<p class="mermaid-error-message">${this.escapeHtml(errorMessage)}</p>
|
||||||
|
</div>
|
||||||
|
<details class="mermaid-error-code">
|
||||||
|
<summary>查看原始代码</summary>
|
||||||
|
<pre><code class="language-mermaid">${this.escapeHtml(code || element.textContent)}</code></pre>
|
||||||
|
</details>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 替换原始代码块
|
||||||
|
element.parentNode.replaceChild(errorContainer, element);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取错误类型
|
||||||
|
* @param {string} errorMessage - 错误信息
|
||||||
|
* @returns {string} 错误类型
|
||||||
|
*/
|
||||||
|
getErrorType(errorMessage) {
|
||||||
|
if (errorMessage.includes('Syntax') || errorMessage.includes('Parse')) {
|
||||||
|
return '语法错误';
|
||||||
|
} else if (errorMessage.includes('Lexical')) {
|
||||||
|
return '词法错误';
|
||||||
|
} else if (errorMessage.includes('Expecting')) {
|
||||||
|
return '格式错误';
|
||||||
|
}
|
||||||
|
return '渲染错误';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTML 转义
|
||||||
|
* @param {string} text - 要转义的文本
|
||||||
|
* @returns {string} 转义后的文本
|
||||||
|
*/
|
||||||
|
escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
},
|
||||||
|
|
||||||
|
// ---------- 样式增强 ----------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用容器样式
|
||||||
|
* @param {HTMLElement} container - 图表容器
|
||||||
|
*/
|
||||||
|
applyStyles(container) {
|
||||||
|
// 添加淡入动画
|
||||||
|
container.style.opacity = '0';
|
||||||
|
setTimeout(() => {
|
||||||
|
container.style.transition = 'opacity 0.3s ease-in';
|
||||||
|
container.style.opacity = '1';
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
// 确保 SVG 响应式
|
||||||
|
const svg = container.querySelector('svg');
|
||||||
|
if (svg) {
|
||||||
|
svg.style.maxWidth = '100%';
|
||||||
|
svg.style.height = 'auto';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ---------- 主题切换监听 ----------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听主题切换事件
|
||||||
|
*/
|
||||||
|
listenThemeSwitch() {
|
||||||
|
// 如果配置了固定主题,不需要监听切换
|
||||||
|
if (this.config && this.config.theme !== 'auto') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听 Argon 主题切换事件
|
||||||
|
document.addEventListener('argon:theme-switched', () => {
|
||||||
|
this.reRenderCharts();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 使用 MutationObserver 监听 darkmode class 变化
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.attributeName === 'class') {
|
||||||
|
const isDarkMode = document.documentElement.classList.contains('darkmode');
|
||||||
|
const currentTheme = isDarkMode ? 'dark' : 'default';
|
||||||
|
|
||||||
|
// 检查主题是否真的改变了
|
||||||
|
const firstChart = document.querySelector('.mermaid-container');
|
||||||
|
if (firstChart && firstChart.dataset.currentTheme !== currentTheme) {
|
||||||
|
this.reRenderCharts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['class']
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logDebug('主题切换监听器已启动');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新渲染所有图表(主题切换时)
|
||||||
|
*/
|
||||||
|
reRenderCharts() {
|
||||||
|
const charts = document.querySelectorAll('.mermaid-container');
|
||||||
|
|
||||||
|
if (charts.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logDebug(`重新渲染 ${charts.length} 个图表`);
|
||||||
|
|
||||||
|
// 更新 Mermaid 主题配置
|
||||||
|
const newTheme = this.getMermaidTheme();
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.mermaid.initialize({
|
||||||
|
startOnLoad: false,
|
||||||
|
theme: newTheme,
|
||||||
|
securityLevel: 'loose',
|
||||||
|
logLevel: this.config.debugMode ? 'debug' : 'error',
|
||||||
|
flowchart: {
|
||||||
|
useMaxWidth: true,
|
||||||
|
htmlLabels: true,
|
||||||
|
curve: 'basis'
|
||||||
|
},
|
||||||
|
sequence: {
|
||||||
|
useMaxWidth: true,
|
||||||
|
wrap: true
|
||||||
|
},
|
||||||
|
gantt: {
|
||||||
|
useMaxWidth: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 重新渲染每个图表
|
||||||
|
charts.forEach((chart, index) => {
|
||||||
|
const code = chart.dataset.mermaidCode;
|
||||||
|
if (!code) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartId = `mermaid-chart-rerender-${Date.now()}-${index}`;
|
||||||
|
|
||||||
|
window.mermaid.render(`mermaid-svg-${chartId}`, code).then(result => {
|
||||||
|
chart.innerHTML = result.svg;
|
||||||
|
chart.dataset.currentTheme = newTheme;
|
||||||
|
|
||||||
|
// 确保 SVG 响应式
|
||||||
|
const svg = chart.querySelector('svg');
|
||||||
|
if (svg) {
|
||||||
|
svg.style.maxWidth = '100%';
|
||||||
|
svg.style.height = 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logDebug(`图表重新渲染成功: ${chartId}`);
|
||||||
|
}).catch(error => {
|
||||||
|
this.logError('图表重新渲染失败', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.logError('重新渲染失败', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ---------- 日志工具 ----------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调试日志
|
||||||
|
* @param {string} message - 日志信息
|
||||||
|
* @param {*} data - 附加数据
|
||||||
|
*/
|
||||||
|
logDebug(message, data) {
|
||||||
|
if (this.config && this.config.debugMode) {
|
||||||
|
if (data) {
|
||||||
|
console.log(`[Argon Mermaid] ${message}`, data);
|
||||||
|
} else {
|
||||||
|
console.log(`[Argon Mermaid] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误日志
|
||||||
|
* @param {string} message - 错误信息
|
||||||
|
* @param {Error} error - 错误对象
|
||||||
|
*/
|
||||||
|
logError(message, error) {
|
||||||
|
console.error(`[Argon Mermaid] ${message}`, error);
|
||||||
|
},
|
||||||
|
|
||||||
|
// ---------- 初始化 ----------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化渲染引擎
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
// 检查是否启用
|
||||||
|
if (typeof window.argonMermaidConfig !== 'undefined' && !window.argonMermaidConfig.enabled) {
|
||||||
|
this.logDebug('Mermaid 支持未启用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 Mermaid 库是否加载
|
||||||
|
if (typeof window.mermaid === 'undefined') {
|
||||||
|
this.logDebug('Mermaid 库未加载,等待加载...');
|
||||||
|
|
||||||
|
// 等待库加载(最多等待 5 秒)
|
||||||
|
let attempts = 0;
|
||||||
|
const checkInterval = setInterval(() => {
|
||||||
|
attempts++;
|
||||||
|
if (typeof window.mermaid !== 'undefined') {
|
||||||
|
clearInterval(checkInterval);
|
||||||
|
this.logDebug('Mermaid 库加载完成');
|
||||||
|
this.initAndRender();
|
||||||
|
} else if (attempts >= 50) {
|
||||||
|
clearInterval(checkInterval);
|
||||||
|
this.logError('Mermaid 库加载超时');
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initAndRender();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化并渲染
|
||||||
|
*/
|
||||||
|
initAndRender() {
|
||||||
|
// 初始化配置
|
||||||
|
if (!this.initConfig()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染所有图表
|
||||||
|
this.renderAllCharts();
|
||||||
|
|
||||||
|
// 监听主题切换
|
||||||
|
this.listenThemeSwitch();
|
||||||
|
|
||||||
|
this.logDebug('Mermaid 渲染引擎初始化完成');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- 启动渲染引擎 ----------
|
||||||
|
|
||||||
|
// 在 DOM 加载完成后初始化
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
MermaidRenderer.init();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// DOM 已加载完成,直接初始化
|
||||||
|
MermaidRenderer.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露到全局(用于 PJAX 等场景)
|
||||||
|
window.ArgonMermaidRenderer = MermaidRenderer;
|
||||||
|
|
||||||
|
})();
|
||||||
|
/* ========== End of Mermaid 图表渲染引擎 ========== */
|
||||||
|
|||||||
Reference in New Issue
Block a user