diff --git a/.kiro/specs/mermaid-support/tasks.md b/.kiro/specs/mermaid-support/tasks.md new file mode 100644 index 0000000..8cd1b01 --- /dev/null +++ b/.kiro/specs/mermaid-support/tasks.md @@ -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 生成的 `` 格式 + - 实现转义字符解码(`\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 任务用于增量验证,确保功能正确性 +- 属性测试验证通用正确性属性 +- 单元测试验证具体示例和边缘情况 +- 集成测试验证端到端流程 + diff --git a/argontheme.js b/argontheme.js index 0d64034..5335ae4 100644 --- a/argontheme.js +++ b/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 = ` +
错误类型: ${errorType}
+ +${this.escapeHtml(code || element.textContent)}
+