diff --git a/.kiro/steering/mermaid-fix-2026-01-27.md b/.kiro/steering/mermaid-fix-2026-01-27.md deleted file mode 100644 index 06c193d..0000000 --- a/.kiro/steering/mermaid-fix-2026-01-27.md +++ /dev/null @@ -1,353 +0,0 @@ -# Mermaid 渲染问题修复 - -## 修复时间 -2026-01-27 - -## 问题描述 - -用户报告了以下 Mermaid 相关问题: - -1. **代码块转换功能被禁用**:`convertMermaidCodeblocks()` 函数第一行有 `return;`,导致 ```mermaid 代码块无法转换 -2. **检测选择器不完整**:`detectMermaidBlocks()` 只检测 `div.mermaid-shortcode`,缺少其他格式的检测 -3. **首页预览显示原始代码**:点击首页文章预览时,显示无格式化的纯文字 Mermaid 代码 - -## 根本原因 - -### 1. 代码块转换被禁用 -在 `argontheme.js` 第 4345 行,`convertMermaidCodeblocks()` 函数开头有一个 `return;` 语句,导致整个函数被跳过: - -```javascript -function convertMermaidCodeblocks(){ - return; // ← 这行导致函数直接返回 - // ... 后面的代码都不会执行 -} -``` - -### 2. 检测选择器不完整 -在 `MermaidRenderer.detectMermaidBlocks()` 中,选择器数组只包含一个元素: - -```javascript -const selectors = [ - 'div.mermaid-shortcode' // 只检测 shortcode 格式 -]; -``` - -缺少对以下格式的检测: -- `div.mermaid-from-codeblock` - 代码块魔改格式 -- `div.mermaid` - 标准格式 -- `pre code.language-mermaid` - Markdown 格式 -- `pre[data-lang="mermaid"]` - 自定义属性格式 -- `code.mermaid` - 简化格式 - -### 3. 首页预览问题 -在三个文章预览模板中,使用 `wp_trim_words()` 直接处理包含 `[mermaid]...[/mermaid]` shortcode 的内容: - -```php -$preview = wp_trim_words(get_the_content('...'), $trim_words_count); -``` - -`wp_trim_words()` 会破坏 shortcode 结构,导致显示原始的 Mermaid 代码文本。 - -## 修复方案 - -### 1. 启用代码块转换功能 - -**文件**:`argontheme.js` - -**修改**:移除 `convertMermaidCodeblocks()` 函数开头的 `return;` 语句 - -```javascript -// 修改前 -function convertMermaidCodeblocks(){ - return; - // ... -} - -// 修改后 -function convertMermaidCodeblocks(){ - // 支持多种代码块格式 - // ... -} -``` - -**效果**: -- ✅ ```mermaid 代码块可以正常转换为 `
` -- ✅ 避免代码高亮干扰 Mermaid 渲染 -- ✅ 支持标准 Markdown 格式 - -### 2. 添加完整的检测选择器 - -**文件**:`argontheme.js` - -**修改**:在 `detectMermaidBlocks()` 中添加完整的选择器列表 - -```javascript -// 修改前 -const selectors = [ - 'div.mermaid-shortcode' -]; - -// 修改后 -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. 修复首页预览问题 - -**文件**: -- `functions.php` -- `template-parts/content-preview-1.php` -- `template-parts/content-preview-2.php` -- `template-parts/content-preview-3.php` - -**修改 1**:在 `functions.php` 中添加辅助函数 - -```php -/** - * 从内容中移除 Mermaid shortcode,用于文章预览 - * 避免在预览中显示原始 Mermaid 代码 - * - * @param string $content 文章内容 - * @return string 移除 Mermaid shortcode 后的内容 - */ -function argon_remove_mermaid_from_preview($content) { - // 移除 [mermaid]...[/mermaid] shortcode - $content = preg_replace('/\[mermaid[^\]]*\].*?\[\/mermaid\]/is', '[Mermaid 图表]', $content); - return $content; -} -``` - -**修改 2**:在三个预览模板中使用辅助函数 - -```php -// 修改前 -if (get_option("argon_hide_shortcode_in_preview") == 'true'){ - $preview = wp_trim_words(do_shortcode(get_the_content('...')), $trim_words_count); -}else{ - $preview = wp_trim_words(get_the_content('...'), $trim_words_count); -} - -// 修改后 -$content_for_preview = get_the_content('...'); -// 移除 Mermaid shortcode,避免在预览中显示原始代码 -$content_for_preview = argon_remove_mermaid_from_preview($content_for_preview); - -if (get_option("argon_hide_shortcode_in_preview") == 'true'){ - $preview = wp_trim_words(do_shortcode($content_for_preview), $trim_words_count); -}else{ - $preview = wp_trim_words($content_for_preview, $trim_words_count); -} -``` - -**效果**: -- ✅ 首页预览中不再显示原始 Mermaid 代码 -- ✅ 用 `[Mermaid 图表]` 占位符替代 -- ✅ 保持预览内容的可读性 - -## 技术细节 - -### 代码块转换流程 - -``` -页面加载/PJAX切换 - ↓ -highlightJsRender() 调用 - ↓ -convertMermaidCodeblocks() 执行 ← 【拦截阶段】 - ↓ - 查找

-    ↓
-    提取纯文本代码
-    ↓
-    创建 
- ↓ - 替换原始代码块 - ↓ -代码高亮处理其他代码块 - ↓ -MermaidRenderer.detectMermaidBlocks() ← 【检测阶段】 - ↓ -MermaidRenderer.renderChart() ← 【渲染阶段】 -``` - -### 支持的 Mermaid 格式 - -#### 1. Shortcode 格式(推荐) - -``` -[mermaid] -flowchart TD - A --> B -[/mermaid] -``` - -**优势**: -- 最稳定的方式 -- 不受代码高亮影响 -- 支持参数配置 - -#### 2. Markdown 代码块格式 - -````markdown -```mermaid -flowchart TD - A --> B -``` -```` - -**优势**: -- 标准 Markdown 语法 -- 编辑器支持好 -- 可以拉起代码高亮 - -**注意**:需要代码块转换功能支持 - -#### 3. HTML 容器格式 - -```html -
-flowchart TD - A --> B -
-``` - -**优势**: -- 直接渲染 -- 兼容性好 - -## 测试验证 - -### 测试场景 1:Shortcode 格式 - -**测试步骤**: -1. 创建新文章 -2. 使用 `[mermaid]...[/mermaid]` 格式插入图表 -3. 发布文章 -4. 查看文章页面 -5. 查看首页预览 - -**预期结果**: -- ✅ 文章页面正确渲染 Mermaid 图表 -- ✅ 首页预览显示 `[Mermaid 图表]` 占位符 - -### 测试场景 2:Markdown 代码块格式 - -**测试步骤**: -1. 创建新文章 -2. 使用 ` ```mermaid ` 格式插入图表 -3. 发布文章 -4. 查看文章页面 - -**预期结果**: -- ✅ 代码块被转换为 `
` -- ✅ 图表正确渲染 -- ✅ 不显示代码高亮的行号和复制按钮 - -### 测试场景 3:PJAX 切换 - -**测试步骤**: -1. 在首页点击文章链接(PJAX 加载) -2. 查看 Mermaid 图表是否渲染 -3. 返回首页(PJAX 加载) -4. 再次点击文章 - -**预期结果**: -- ✅ PJAX 切换后图表正常渲染 -- ✅ 不会重复渲染 -- ✅ 主题切换正常工作 - -## 相关文件 - -### 修改的文件 -- `argontheme.js` - 启用代码块转换,添加完整选择器 -- `functions.php` - 添加 `argon_remove_mermaid_from_preview()` 函数 -- `template-parts/content-preview-1.php` - 修复预览显示 -- `template-parts/content-preview-2.php` - 修复预览显示 -- `template-parts/content-preview-3.php` - 修复预览显示 - -### 相关文档 -- `docs/mermaid-user-guide.md` - 用户使用指南 -- `docs/mermaid-developer-guide.md` - 开发者文档 -- `docs/mermaid-troubleshooting.md` - 故障排查指南 - -## Git 提交 - -```bash -git commit -m "fix: 修复 Mermaid 渲染问题 - -- 启用代码块转换功能(移除 convertMermaidCodeblocks 中的 return 语句) -- 添加完整的 Mermaid 代码块检测选择器 -- 修复首页预览中显示原始 Mermaid 代码的问题 -- 添加 argon_remove_mermaid_from_preview 函数过滤预览内容 -- 更新三个文章预览模板,在预览中用 [Mermaid 图表] 替代原始代码" -``` - -**提交哈希**:135c226 - -## 后续建议 - -### 1. 功能增强 - -- [ ] 添加预览中的 Mermaid 图表缩略图 -- [ ] 支持更多 Mermaid 图表类型 -- [ ] 添加图表导出功能 - -### 2. 性能优化 - -- [ ] 延迟加载 Mermaid 库 -- [ ] 缓存渲染结果 -- [ ] 优化大型图表的渲染 - -### 3. 用户体验 - -- [ ] 添加图表编辑器 -- [ ] 提供图表模板 -- [ ] 改进错误提示 - -## 常见问题 - -### Q1: 为什么代码块转换被禁用了? - -A: 可能是在之前的某次修复中,为了临时解决某个问题而添加了 `return;` 语句,但忘记移除。 - -### Q2: 如何确保 PJAX 兼容性? - -A: 代码块转换在 `highlightJsRender()` 中调用,该函数已在 PJAX 回调中注册,因此自动支持 PJAX。 - -### Q3: 为什么不在预览中渲染 Mermaid 图表? - -A: 因为: -1. 预览内容会被 `wp_trim_words()` 截断,可能破坏图表代码 -2. 在列表页渲染大量图表会影响性能 -3. 使用占位符更简洁明了 - -### Q4: 如何添加新的 Mermaid 格式支持? - -A: 在 `detectMermaidBlocks()` 的 `selectors` 数组中添加新的选择器,并在 `extractMermaidCode()` 中添加对应的提取逻辑。 - -## 总结 - -本次修复解决了 Mermaid 渲染的三个主要问题: - -1. ✅ **启用代码块转换**:移除了阻止转换的 `return;` 语句 -2. ✅ **完善检测机制**:添加了完整的选择器列表,支持多种格式 -3. ✅ **修复预览显示**:在预览中用占位符替代原始代码 - -用户现在可以: -- 使用 `[mermaid]...[/mermaid]` shortcode 格式(推荐) -- 使用 ` ```mermaid ` Markdown 代码块格式 -- 在首页预览中看到友好的占位符而不是原始代码 -- 享受完整的 Mermaid 图表渲染功能 - -所有修改都遵循了 Argon 主题的代码规范,并保持了向后兼容性。 diff --git a/ai-summary-query.php b/ai-summary-query.php index fd680e3..e0972ea 100644 --- a/ai-summary-query.php +++ b/ai-summary-query.php @@ -178,6 +178,23 @@ if (!empty($query_code)) { 'code' => $query_code ]; + $log_table = $wpdb->prefix . 'argon_ai_query_log'; + $latest = $wpdb->get_row($wpdb->prepare( + "SELECT provider, model FROM {$log_table} WHERE scenario = %s AND status = 'success' AND post_id = %d ORDER BY id DESC LIMIT 1", + 'summary', + $post_id + ), ARRAY_A); + if (is_array($latest)) { + if (!empty($latest['provider']) && $latest['provider'] !== $result['provider']) { + $result['provider'] = $latest['provider']; + update_post_meta($post_id, '_argon_ai_summary_provider', $result['provider']); + } + if (isset($latest['model']) && $latest['model'] !== $result['model']) { + $result['model'] = $latest['model']; + update_post_meta($post_id, '_argon_ai_summary_model', $result['model']); + } + } + $provider_names = [ 'openai' => 'OpenAI', 'anthropic' => 'Anthropic', @@ -236,8 +253,30 @@ if (!empty($query_code)) { ]; // 获取 AI 配置信息 - $result['provider'] = get_option('argon_ai_summary_provider', 'openai'); - $result['model'] = get_option('argon_ai_summary_model', ''); + $result['provider'] = get_comment_meta($comment_id, '_argon_spam_detection_provider', true); + $result['model'] = get_comment_meta($comment_id, '_argon_spam_detection_model', true); + $log_table = $wpdb->prefix . 'argon_ai_query_log'; + $latest = $wpdb->get_row($wpdb->prepare( + "SELECT provider, model FROM {$log_table} WHERE scenario = %s AND status = 'success' AND comment_id = %d ORDER BY id DESC LIMIT 1", + 'spam_detection', + $comment_id + ), ARRAY_A); + if (is_array($latest)) { + if (!empty($latest['provider']) && $latest['provider'] !== $result['provider']) { + $result['provider'] = $latest['provider']; + update_comment_meta($comment_id, '_argon_spam_detection_provider', $result['provider']); + } + if (isset($latest['model']) && $latest['model'] !== $result['model']) { + $result['model'] = $latest['model']; + update_comment_meta($comment_id, '_argon_spam_detection_model', $result['model']); + } + } + if (empty($result['provider'])) { + $result['provider'] = get_option('argon_ai_summary_provider', 'openai'); + } + if (empty($result['model'])) { + $result['model'] = get_option('argon_ai_summary_model', ''); + } $provider_names = [ 'openai' => 'OpenAI', @@ -702,6 +741,24 @@ if (current_user_can('manage_options')): foreach ($summaries as $summary) { $post = get_post($summary->post_id); if ($post) { + $summary_provider = get_post_meta($summary->post_id, '_argon_ai_summary_provider', true); + $summary_model = get_post_meta($summary->post_id, '_argon_ai_summary_model', true); + $log_table = $wpdb->prefix . 'argon_ai_query_log'; + $latest = $wpdb->get_row($wpdb->prepare( + "SELECT provider, model FROM {$log_table} WHERE scenario = %s AND status = 'success' AND post_id = %d ORDER BY id DESC LIMIT 1", + 'summary', + $summary->post_id + ), ARRAY_A); + if (is_array($latest)) { + if (!empty($latest['provider']) && $latest['provider'] !== $summary_provider) { + $summary_provider = $latest['provider']; + update_post_meta($summary->post_id, '_argon_ai_summary_provider', $summary_provider); + } + if (isset($latest['model']) && $latest['model'] !== $summary_model) { + $summary_model = $latest['model']; + update_post_meta($summary->post_id, '_argon_ai_summary_model', $summary_model); + } + } $all_records[] = [ 'type' => 'summary', 'code' => $summary->code, @@ -714,8 +771,8 @@ if (current_user_can('manage_options')): 'post_title' => get_the_title($summary->post_id), 'post_url' => get_permalink($summary->post_id), 'summary' => get_post_meta($summary->post_id, '_argon_ai_summary', true), - 'model' => get_post_meta($summary->post_id, '_argon_ai_summary_model', true), - 'provider' => get_post_meta($summary->post_id, '_argon_ai_summary_provider', true), + 'model' => $summary_model, + 'provider' => $summary_provider, 'generated_time' => $summary->time ] ]; @@ -726,6 +783,30 @@ if (current_user_can('manage_options')): $comment = get_comment($detection->comment_id); if ($comment) { $detection_result = get_comment_meta($detection->comment_id, '_argon_spam_detection_result', true); + $provider = get_comment_meta($detection->comment_id, '_argon_spam_detection_provider', true); + $model = get_comment_meta($detection->comment_id, '_argon_spam_detection_model', true); + $log_table = $wpdb->prefix . 'argon_ai_query_log'; + $latest = $wpdb->get_row($wpdb->prepare( + "SELECT provider, model FROM {$log_table} WHERE scenario = %s AND status = 'success' AND comment_id = %d ORDER BY id DESC LIMIT 1", + 'spam_detection', + $detection->comment_id + ), ARRAY_A); + if (is_array($latest)) { + if (!empty($latest['provider']) && $latest['provider'] !== $provider) { + $provider = $latest['provider']; + update_comment_meta($detection->comment_id, '_argon_spam_detection_provider', $provider); + } + if (isset($latest['model']) && $latest['model'] !== $model) { + $model = $latest['model']; + update_comment_meta($detection->comment_id, '_argon_spam_detection_model', $model); + } + } + if (empty($provider)) { + $provider = get_option('argon_ai_summary_provider', 'openai'); + } + if (empty($model)) { + $model = get_option('argon_ai_summary_model', ''); + } $all_records[] = [ 'type' => 'spam_detection', 'code' => $detection->code, @@ -747,6 +828,8 @@ if (current_user_can('manage_options')): 'suggestion' => isset($detection_result['suggestion']) ? $detection_result['suggestion'] : '', 'analysis' => isset($detection_result['analysis']) ? $detection_result['analysis'] : '', 'action' => isset($detection_result['action']) ? $detection_result['action'] : '', + 'provider' => $provider, + 'model' => $model, 'detection_time' => $detection->time ] ]; diff --git a/argon-performance.js b/argon-performance.js index 1ef6498..abea3bc 100644 --- a/argon-performance.js +++ b/argon-performance.js @@ -696,23 +696,58 @@ class ArgonResourceLoader { return this.loading.get(name); } - // 创建新的加载 Promise + const timeoutValue = (typeof argonConfig !== 'undefined' && argonConfig.resource_load_timeout) ? parseInt(argonConfig.resource_load_timeout, 10) : 8000; + const timeout = Number.isFinite(timeoutValue) ? timeoutValue : 8000; + const promise = new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = url; script.async = true; - script.onload = () => { + let finished = false; + let timeoutId = null; + + const clearState = () => { + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + script.onload = null; + script.onerror = null; + }; + + const resolveSuccess = () => { + if (finished) return; + finished = true; + clearState(); this.loaded.add(name); this.loading.delete(name); resolve(); }; - script.onerror = () => { + const rejectFailure = (error) => { + if (finished) return; + finished = true; + clearState(); + if (script.parentNode) { + script.parentNode.removeChild(script); + } this.loading.delete(name); - reject(new Error(`Failed to load ${name} from ${url}`)); + reject(error); }; + script.onload = () => { + resolveSuccess(); + }; + + script.onerror = () => { + rejectFailure(new Error(`Failed to load ${name} from ${url}`)); + }; + + timeoutId = setTimeout(() => { + rejectFailure(new Error(`Timeout loading ${name} from ${url}`)); + }, timeout); + document.head.appendChild(script); }); diff --git a/argontheme.js b/argontheme.js index 6f7d5e4..3b0056c 100644 --- a/argontheme.js +++ b/argontheme.js @@ -2876,42 +2876,6 @@ function cleanupTippyInstances() { } } -/** - * 清理 Mermaid 实例 - * @returns {void} - */ -function cleanupMermaidInstances() { - try { - // 清理已渲染的图表记录 - if (typeof MermaidRenderer !== 'undefined' && MermaidRenderer.rendered) { - const count = MermaidRenderer.rendered.size; - MermaidRenderer.rendered.clear(); - if (count > 0) { - ArgonDebug.log(`已清理 ${count} 个 Mermaid 图表记录`); - } - } - - // 移除 Mermaid 容器的事件监听器 - document.querySelectorAll('.mermaid-container').forEach(function(container) { - // 移除工具栏事件监听器 - const toolbar = container.querySelector('.mermaid-toolbar'); - if (toolbar) { - // 克隆节点以移除所有事件监听器 - const newToolbar = toolbar.cloneNode(true); - toolbar.parentNode.replaceChild(newToolbar, toolbar); - } - - // 清理全屏模式 - if (container._fullscreenCleanup) { - container._fullscreenCleanup(); - } - }); - - ArgonDebug.log('Mermaid 实例已清理'); - } catch(e) { - ArgonDebug.warn('清理 Mermaid 实例失败:', e); - } -} /** * 清理动态添加的 style 标签 @@ -2957,7 +2921,6 @@ function cleanupDynamicScripts() { */ function cleanupEventListeners() { try { - // 清理 Mermaid 相关的事件监听器 // 注意:大部分事件使用事件委托,不需要手动清理 // 这里只清理特定的非委托事件 @@ -2977,7 +2940,6 @@ function cleanupEventListeners() { * - Lazyload Observer * - Zoomify 实例 * - Tippy 实例 - * - Mermaid 实例 * - 动态 style 标签 * - 动态 script 标签 * - 事件监听器 @@ -2990,9 +2952,7 @@ function cleanupPjaxResources() { // 按顺序清理各类资源 cleanupLazyloadObserver(); cleanupZoomifyInstances(); - cleanupTippyInstances(); - cleanupMermaidInstances(); - cleanupDynamicStyles(); + cleanupTippyInstances(); cleanupDynamicStyles(); cleanupDynamicScripts(); cleanupEventListeners(); @@ -3164,8 +3124,8 @@ $.pjax.defaults.fragment = ['#primary', '#leftbar_part1_menu', '#leftbar_part2_i /* * PJAX 事件处理优化说明: - * 1. pjax:beforeReplace - 统一清理资源(LazyLoad Observer、Zoomify、Tippy、Mermaid) - * 2. pjax:complete - 单次初始化所有功能模块,添加错误处理,包含 Mermaid 渲染 + * 1. pjax:beforeReplace - 统一清理资源(LazyLoad Observer、Zoomify、Tippy) + * 2. pjax:complete - 单次初始化所有功能模块,添加错误处理,初始化所有功能模块 * 3. pjax:end - 只处理特定任务(移动端目录重置、GT4 验证码重置) * 4. 移除了重复的初始化调用,避免资源泄漏和性能问题 * @@ -3195,7 +3155,7 @@ $(document).pjax("a[href]:not([no-pjax]):not(.no-pjax):not([target='_blank']):no }).on('pjax:beforeReplace', function(e, dom) { // ========== 需求 1.1-1.4: 清理旧页面的所有资源 ========== // 调用统一的资源清理管理器 - // 清理内容:Lazyload Observer、Zoomify、Tippy、Mermaid 实例、动态标签 + // 清理内容:Lazyload Observer、Zoomify、Tippy 实例、动态标签 cleanupPjaxResources(); // 更新 UI 状态 @@ -3260,18 +3220,6 @@ $(document).pjax("a[href]:not([no-pjax]):not(.no-pjax):not([target='_blank']):no try { calcHumanTimesOnPage(); } catch (err) { ArgonDebug.error('calcHumanTimesOnPage failed:', err); } try { foldLongComments(); } catch (err) { ArgonDebug.error('foldLongComments failed:', err); } try { foldLongShuoshuo(); } catch (err) { ArgonDebug.error('foldLongShuoshuo failed:', err); } - - // Mermaid 图表渲染(需求 3.6: 页面切换时重新渲染) - try { - if (typeof MermaidRenderer !== 'undefined' && MermaidRenderer.init && !MermaidRenderer.initialized) { - MermaidRenderer.init(); - } - if (typeof MermaidRenderer !== 'undefined' && MermaidRenderer.renderAllCharts) { - MermaidRenderer.renderAllCharts(); - } - } catch (err) { - ArgonDebug.error('MermaidRenderer.renderAllCharts failed:', err); - } try { handleHashNavigation(); } catch (err) { ArgonDebug.error('handleHashNavigation failed:', err); } $("html").trigger("resize"); @@ -4336,99 +4284,8 @@ function getCodeFromBlock(block){ return res; } -/** - * 在代码高亮之前转换 Mermaid 代码块 - * 将
 转换为 
- * 这样可以避免代码高亮干扰 Mermaid 渲染 - */ -function convertMermaidCodeblocks(){ - // 支持多种代码块格式 - const selectors = [ - 'pre > code.language-mermaid', // 标准 Markdown 格式(最常见) - 'pre > code.mermaid', // 简化格式 - 'code.language-mermaid', // 无 pre 包裹 - 'pre[data-lang="mermaid"]' // 自定义属性格式 - ]; - - let processedCount = 0; - - selectors.forEach(selector => { - document.querySelectorAll(selector).forEach(element => { - // 避免重复处理 - if (element.dataset.mermaidProcessed) { - return; - } - - // 提取代码 - let code = element.textContent; - - // 清理代码:移除前后空白,但保留内部换行 - code = code.trim(); - - // 如果代码为空,跳过 - if (!code) { - return; - } - - // 移除每行开头的统一缩进(保持相对缩进) - const lines = code.split('\n'); - if (lines.length > 1) { - // 找到最小缩进 - let minIndent = Infinity; - lines.forEach(line => { - if (line.trim().length > 0) { - const indent = line.match(/^\s*/)[0].length; - if (indent < minIndent) { - minIndent = indent; - } - } - }); - - // 移除最小缩进 - if (minIndent > 0 && minIndent !== Infinity) { - code = lines.map(line => { - if (line.trim().length === 0) { - return ''; - } - return line.substring(minIndent); - }).join('\n'); - } - } - - // 创建容器 - const container = document.createElement('div'); - container.className = 'mermaid mermaid-from-codeblock'; - container.textContent = code; - container.dataset.processed = 'true'; - - // 调试日志 - if (typeof console !== 'undefined' && console.log) { - console.log('[Mermaid 转换] 代码长度:', code.length); - console.log('[Mermaid 转换] 代码内容:', code.substring(0, 200)); - console.log('[Mermaid 转换] 容器 textContent:', container.textContent.substring(0, 200)); - } - - // 替换元素 - const targetElement = element.closest('pre') || element; - if (targetElement.parentNode) { - targetElement.parentNode.replaceChild(container, targetElement); - processedCount++; - } - - // 标记已处理 - element.dataset.mermaidProcessed = 'true'; - }); - }); - - if (processedCount > 0 && typeof console !== 'undefined' && console.log) { - console.log('[Mermaid] 转换了 ' + processedCount + ' 个代码块'); - } -} -function highlightJsRender(){ - // 在代码高亮之前,先处理 Mermaid 代码块 - convertMermaidCodeblocks(); - +function highlightJsRender(){ if (typeof(hljs) == "undefined"){ return; } @@ -4448,14 +4305,6 @@ function highlightJsRender(){ if ($(block).hasClass("no-hljs")){ return; } - // 跳过 mermaid 代码块(避免代码高亮干扰 mermaid 渲染) - if ($(block).hasClass("language-mermaid") || $(block).hasClass("mermaid")){ - return; - } - // 跳过在 .mermaid 容器内的代码块 - if ($(block).closest('.mermaid').length > 0){ - return; - } $(block).parent().attr("id", randomString()); hljs.highlightBlock(block); hljs.lineNumbersBlock(block, {singleLine: true}); @@ -4813,1942 +4662,3 @@ void 0; }; } })(); - -// ========================================================================== -// Mermaid 图表渲染引擎 -// ========================================================================== -(function() { - 'use strict'; - - // ---------- 配置和状态管理 ---------- - - /** - * Mermaid 渲染引擎配置 - */ - const MermaidRenderer = { - initialized: false, - rendered: new Set(), // 已渲染的图表 ID 集合 - config: null, - compatibilityFallbackAttempted: false, - - /** - * 等待 Mermaid 库加载 - * 需求 2.6: 清除缓存后首次加载时等待 Mermaid 库完全加载 - * 需求 4.1: 开始渲染前检查 Mermaid 库是否已加载 - * 需求 4.2: Mermaid 库未加载时等待库加载或显示错误提示 - * @param {number} timeout - 超时时间(毫秒),默认 10000ms (增加超时时间以应对慢速网络) - * @returns {Promise} 是否加载成功 - */ - waitForMermaid(timeout = 10000) { - return new Promise((resolve) => { - // 如果已经加载,直接返回成功 - if (typeof window.mermaid !== 'undefined') { - this.logDebug('Mermaid 库已加载'); - resolve(true); - return; - } - - this.logDebug('Mermaid 库未加载,开始等待...'); - - const startTime = Date.now(); - const checkInterval = setInterval(() => { - // 检查是否已加载 - if (typeof window.mermaid !== 'undefined') { - clearInterval(checkInterval); - const loadTime = Date.now() - startTime; - this.logDebug(`Mermaid 库加载成功,耗时 ${loadTime}ms`); - resolve(true); - } - // 检查是否超时 - else if (Date.now() - startTime > timeout) { - clearInterval(checkInterval); - this.logError(`Mermaid 库加载超时(${timeout}ms)`); - - // 尝试手动触发降级加载 - if (typeof window.argonMermaidLoadFallback === 'function') { - this.logDebug('尝试手动触发降级加载'); - window.argonMermaidLoadFallback(); - // 给降级加载一点时间 - setTimeout(() => { - if (typeof window.mermaid !== 'undefined') { - resolve(true); - } else { - resolve(false); - } - }, 2000); - } else { - resolve(false); - } - } - }, 100); // 每 100ms 检查一次 - }); - }, - - /** - * 初始化 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: [], - libraryLoadedByPlugin: false - }; - } else { - this.config = window.argonMermaidConfig; - } - - // 如果库由插件加载,记录日志 - if (this.config.libraryLoadedByPlugin) { - this.logDebug('Mermaid 库由插件加载,主题只提供样式增强'); - } - - // 获取当前主题 - const theme = this.getMermaidTheme(); - - // 配置 Mermaid - // 需求 2.1-2.4: 支持多种图表类型,设置正确的主题和安全级别 - try { - // 检查 initialize 是否存在(兼容旧版本) - if (typeof window.mermaid.initialize === 'function') { - window.mermaid.initialize({ - startOnLoad: false, // 手动控制渲染时机 - theme: theme, // 根据网站主题自动切换 - securityLevel: 'loose', // 允许 HTML 标签 - logLevel: this.config.debugMode ? 'debug' : 'error', - // 流程图配置 (需求 2.2) - flowchart: { - useMaxWidth: true, - htmlLabels: true, - curve: 'basis' - }, - // 时序图配置 - sequence: { - useMaxWidth: true, - wrap: true - }, - // 甘特图配置 - gantt: { - useMaxWidth: true - }, - // ER 图配置 (需求 2.3) - er: { - useMaxWidth: true, - layoutDirection: 'TB' - }, - // 状态图配置 (需求 2.4) - stateDiagram: { - useMaxWidth: true - }, - // 类图配置 - class: { - useMaxWidth: true - }, - // 饼图配置 - pie: { - useMaxWidth: true - }, - // Git 图配置 - gitGraph: { - useMaxWidth: true - }, - // 用户旅程图配置 - journey: { - useMaxWidth: true - } - }); - this.initialized = true; - this.logDebug('Mermaid 配置初始化成功', { theme }); - return true; - } else { - this.logError('Mermaid.initialize 方法不存在'); - return false; - } - } 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 代码块 - * 需求 3.1-3.5: 扫描页面中的 Mermaid 代码块并过滤已渲染的块 - * @returns {HTMLElement[]} 未渲染的 Mermaid 代码块数组 - */ - detectMermaidBlocks() { - const blocks = []; - const seen = new Set(); - const selectors = [ - 'div.mermaid-shortcode', // Shortcode 格式(推荐) - 'div.mermaid-from-codeblock', // 代码块魔改格式 - 'div.mermaid', // 标准格式 - 'pre code.language-mermaid', // Markdown 格式(降级) - 'pre[data-lang="mermaid"]', // 自定义属性格式 - 'code.mermaid' // 简化格式 - ]; - - selectors.forEach(selector => { - document.querySelectorAll(selector).forEach(element => { - if (this.isRendered(element)) { - return; - } - if (seen.has(element)) { - return; - } - blocks.push(element); - seen.add(element); - }); - }); - - this.logDebug(`检测到 ${blocks.length} 个未渲染的 Mermaid 代码块`); - return blocks; - }, - - /** - * 检查代码块是否已渲染 - * 需求 3.5: 避免重复渲染已渲染的代码块 - * @param {HTMLElement} element - 代码块元素 - * @returns {boolean} 是否已渲染 - */ - isRendered(element) { - // 检查元素是否有渲染标记 - if (element.hasAttribute('data-mermaid-rendered')) { - return true; - } - - if (element.classList && element.classList.contains('mermaid-error-container')) { - return true; - } - - if (element.classList && element.classList.contains('mermaid-loading')) { - return true; - } - - if (element.classList && element.classList.contains('mermaid-container')) { - return true; - } - - if (element.tagName === 'DIV' && element.classList.contains('mermaid') && element.querySelector('svg')) { - return true; - } - - // 检查元素是否在 mermaid-container 容器中 - if (element.closest('.mermaid-container') !== null) { - return true; - } - - return false; - }, - /** - * 提取代码块内容 - * @param {HTMLElement} element - 代码块元素 - * @returns {string} Mermaid 代码 - */ - extractMermaidCode(element) { - let code = ''; - - // 处理 Shortcode 格式(推荐) - if (element.classList.contains('mermaid-shortcode')) { - code = element.textContent; - this.logDebug('从 Shortcode 格式提取代码,长度: ' + code.length); - this.logDebug('代码内容: ' + code.substring(0, 200)); - } - // 处理代码块魔改格式(新增) - else if (element.classList.contains('mermaid-from-codeblock')) { - code = element.textContent; - this.logDebug('从代码块魔改格式提取代码,长度: ' + code.length); - this.logDebug('代码内容: ' + code.substring(0, 200)); - this.logDebug('元素 HTML: ' + element.outerHTML.substring(0, 300)); - } - // 处理 Markdown 容器语法格式 - else if (element.classList.contains('mermaid-container-block')) { - code = element.textContent; - this.logDebug('从 Markdown 容器语法提取代码,长度: ' + code.length); - this.logDebug('代码内容: ' + code.substring(0, 200)); - } - // 根据不同的元素类型提取代码 - else if (element.tagName === 'DIV' && element.classList.contains('mermaid')) { - // 检查是否包含 WP-Markdown 生成的 script 标签 - const scriptTag = element.querySelector('script'); - if (scriptTag) { - // 提取 document.write() 中的内容 - const scriptContent = scriptTag.textContent || scriptTag.innerText; - this.logDebug('检测到 script 标签,原始内容: ' + scriptContent.substring(0, 100)); - - // 使用更精确的正则:匹配 document.write() 中的字符串内容 - // 支持双引号和单引号,支持转义字符 - // 使用贪婪匹配 [\s\S]* 来匹配包括换行在内的所有字符 - let match = scriptContent.match(/document\.write\s*\(\s*["']([\s\S]*?)["']\s*\)/); - - if (match && match[1]) { - code = match[1]; - this.logDebug('从 document.write() 提取到代码,长度: ' + code.length); - } else { - // 如果没有匹配到 document.write(),尝试直接提取引号内的内容 - match = scriptContent.match(/["']([\s\S]*?)["']/); - if (match && match[1]) { - code = match[1]; - this.logDebug('从引号内提取到代码,长度: ' + code.length); - } else { - // 最后的降级方案:获取除 script 标签外的文本内容 - const clonedElement = element.cloneNode(true); - const scripts = clonedElement.querySelectorAll('script'); - scripts.forEach(script => script.remove()); - code = clonedElement.innerText || clonedElement.textContent; - this.logDebug('使用降级方案提取代码'); - } - } - - // 重要:移除 script 标签,避免重复渲染 - // WP-Markdown 会在 script 后面再输出一次代码 - scriptTag.remove(); - } else { - // 检查是否包含块级元素或换行标签 - if (element.querySelector('br') || element.querySelector('p') || element.querySelector('div')) { - const clonedElement = element.cloneNode(true); - clonedElement.querySelectorAll('script').forEach(script => script.remove()); - clonedElement.querySelectorAll('br').forEach(br => br.replaceWith('\n')); - clonedElement.querySelectorAll('p,div').forEach(node => { - node.appendChild(document.createTextNode('\n')); - }); - code = clonedElement.textContent || ''; - this.logDebug('检测到 HTML 结构,从 innerText 提取代码'); - } else { - code = element.textContent || ''; - this.logDebug('未检测到 HTML 结构,从 textContent 提取代码'); - } - } - } else if (element.tagName === 'CODE') { - code = element.textContent || ''; - this.logDebug('从 CODE 标签提取'); - } else if (element.tagName === 'PRE') { - const codeElement = element.querySelector('code'); - if (codeElement) { - code = codeElement.textContent || ''; - } else { - code = element.textContent || ''; - } - this.logDebug('从 PRE 标签提取'); - } - - // 记录原始提取的代码(调试用) - this.logDebug('原始提取的代码长度: ' + code.length); - this.logDebug('原始代码前100字符: ' + code.substring(0, 100)); - - // 解码 HTML 实体(WordPress 可能会转义 HTML) - code = code - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, "'"); - - // 解码转义字符(必须在 HTML 实体解码之后) - code = code - .replace(/\\n/g, '\n') - .replace(/\\t/g, '\t') - .replace(/\\r/g, '\r') - .replace(/\\"/g, '"') - .replace(/\\'/g, "'") - .replace(/\\\\/g, '\\'); - - // 清理代码:移除首尾空白,但保留内部换行 - code = code.trim(); - - // 验证代码完整性 - if (code.length < 10) { - this.logError('提取的代码过短,可能不完整: ' + code); - } - - this.logDebug('最终提取的代码长度: ' + code.length); - this.logDebug('最终代码前200字符: ' + code.substring(0, 200) + (code.length > 200 ? '...' : '')); - - return code; - }, - - convertLegacySyntax(code) { - let updated = code; - if (/^\s*flowchart\b/i.test(updated)) { - updated = updated.replace(/^\s*flowchart\b/i, 'graph'); - } - if (/^\s*stateDiagram-v2\b/i.test(updated)) { - updated = updated.replace(/^\s*stateDiagram-v2\b/i, 'stateDiagram'); - } - return updated; - }, - - shouldRetryWithGraphSyntax(error, code, forceLegacy) { - if (!/^\s*(flowchart|stateDiagram-v2)\b/i.test(code || '')) { - return false; - } - if (forceLegacy) { - return true; - } - if (error && error.hash && Array.isArray(error.hash.expected)) { - const expected = error.hash.expected.join(' '); - const tokenText = String(error.hash.text || '').toLowerCase(); - if (expected.includes('GRAPH') && tokenText === 'flowchart') { - return true; - } - if ((expected.includes('STATE') || expected.includes('SD')) && tokenText === 'statediagram-v2') { - return true; - } - } - const message = String(error && error.message ? error.message : '').toLowerCase(); - return message.includes('graph') || message.includes('state'); - }, - - needsModernMermaid(code) { - return /^\s*(flowchart|erDiagram|stateDiagram|stateDiagram-v2)\b/i.test(code || ''); - }, - - tryUpgradeMermaidLibrary(element, code) { - if (this.compatibilityFallbackAttempted) { - return false; - } - if (!this.needsModernMermaid(code)) { - return false; - } - if (typeof window.argonMermaidLoadFallback !== 'function') { - return false; - } - this.compatibilityFallbackAttempted = true; - const container = document.createElement('div'); - container.className = 'mermaid'; - container.textContent = code; - if (element && element.parentNode) { - element.parentNode.replaceChild(container, element); - } - window.argonMermaidLoadFallback(); - return true; - }, - - // ---------- 渲染引擎 ---------- - - /** - * 批量渲染所有 Mermaid 图表 - * 需求 2.6: 清除缓存后首次加载时等待 Mermaid 库完全加载 - * 需求 4.1: 开始渲染前检查 Mermaid 库是否已加载 - * 需求 18.1: 使用批量渲染,避免阻塞主线程 - * 需求 18.4: 延迟渲染视口外的图表,优先渲染可见图表 - * @returns {Promise} - */ - async renderAllCharts() { - // 需求 4.1-4.2: 等待 Mermaid 库加载 - if (!await this.waitForMermaid()) { - this.logError('Mermaid 库加载失败,无法渲染图表'); - return; - } - - if (!this.initialized) { - this.logDebug('Mermaid 未初始化,跳过渲染'); - return; - } - - const startRender = (blocks) => { - this.logDebug(`准备渲染 ${blocks.length} 个图表 (Lazy Load)`); - if (this.observer) { - this.observer.disconnect(); - } - this.observer = new IntersectionObserver((entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - const block = entry.target; - const index = blocks.indexOf(block); - requestAnimationFrame(() => { - this.renderChart(block, index); - }); - observer.unobserve(block); - } - }); - }, { - rootMargin: '200px 0px', - threshold: 0.01 - }); - blocks.forEach(block => { - this.observer.observe(block); - }); - }; - - if (this.domObserver) { - this.domObserver.disconnect(); - this.domObserver = null; - } - - const blocks = this.detectMermaidBlocks(); - if (blocks.length === 0) { - this.logDebug('未找到 Mermaid 代码块'); - if (typeof MutationObserver === 'undefined') { - return; - } - let finished = false; - const waitStart = Date.now(); - const stopWaiting = (message) => { - if (finished) { - return; - } - finished = true; - if (this.domObserver) { - this.domObserver.disconnect(); - this.domObserver = null; - } - if (message) { - this.logDebug(message); - } - }; - const tryRender = () => { - const pending = this.detectMermaidBlocks(); - if (pending.length > 0) { - stopWaiting(); - startRender(pending); - return true; - } - return false; - }; - const maxWait = 4000; - this.domObserver = new MutationObserver(() => { - if (tryRender()) { - return; - } - if (Date.now() - waitStart > maxWait) { - stopWaiting('等待 Mermaid 代码块超时'); - } - }); - this.domObserver.observe(document.body, { - childList: true, - subtree: true - }); - setTimeout(() => { - if (!finished) { - if (!tryRender()) { - stopWaiting('等待 Mermaid 代码块超时'); - } - } - }, maxWait); - return; - } - - startRender(blocks); - }, - - /** - * 渲染单个图表 - * 需求 18.5: 图表渲染失败不阻塞其他图表的渲染 - * @param {HTMLElement} element - 代码块元素 - * @param {number} index - 图表索引 - */ - renderChart(element, index) { - const chartId = `mermaid-chart-${Date.now()}-${index}`; - - // 需求 18.5: 使用 try-catch 包裹整个渲染过程 - try { - // 检查是否已经是错误容器(避免重复处理错误) - if (element.classList && element.classList.contains('mermaid-error-container')) { - this.logDebug(`元素已经是错误容器,跳过: ${chartId}`); - return; - } - - // 检查是否已经是渲染成功的容器(避免重复渲染) - if (element.classList && element.classList.contains('mermaid-container') && element.dataset.mermaidCode) { - this.logDebug(`图表已成功渲染,跳过: ${chartId}`); - return; - } - - // 检查是否已渲染(避免重复渲染) - if (this.rendered.has(element)) { - this.logDebug(`图表已渲染,跳过: ${chartId}`); - return; - } - - // 提取代码 - const rawCode = this.extractMermaidCode(element); - const code = rawCode; - - if (!code) { - this.logDebug(`代码块为空,跳过: ${chartId}`); - return; - } - - this.logDebug(`准备渲染图表: ${chartId}, 代码长度: ${code.length}`); - this.logDebug(`代码内容: ${code.substring(0, 200)}`); - - // 需求 18.2: 创建加载动画容器 - const loadingContainer = document.createElement('div'); - loadingContainer.className = 'mermaid-loading'; - loadingContainer.innerHTML = ` -
-
正在渲染图表...
- `; - - // 替换原始元素为加载动画 - if (element.parentNode) { - element.parentNode.replaceChild(loadingContainer, element); - } - - // 创建最终容器 - const container = document.createElement('div'); - container.className = 'mermaid-container'; - container.id = chartId; - - // 检查 Mermaid API - if (typeof window.mermaid === 'undefined') { - this.logError('Mermaid 库未加载'); - this.handleRenderError(loadingContainer, new Error('Mermaid 库未加载'), code); - return; - } - - if (typeof window.mermaid.render !== 'function') { - this.logError('Mermaid.render 方法不存在'); - this.handleRenderError(loadingContainer, new Error('Mermaid.render 方法不存在'), code); - return; - } - - const onRenderSuccess = (svg, bindFunctions) => { - container.innerHTML = svg; - container.dataset.mermaidCode = code; - container.dataset.currentTheme = this.getMermaidTheme(); - container.setAttribute('data-mermaid-rendered', 'true'); - if (loadingContainer.parentNode) { - loadingContainer.parentNode.replaceChild(container, loadingContainer); - } - this.rendered.add(container); - this.applyStyles(container); - if (typeof bindFunctions === 'function') { - try { - bindFunctions(container); - } catch (bindError) { - this.logError('Mermaid bindFunctions 执行失败', bindError); - } - } - this.logDebug(`图表渲染成功: ${chartId}`); - }; - - const renderResult = window.mermaid.render(`mermaid-svg-${chartId}`, code); - - if (renderResult && typeof renderResult.then === 'function') { - renderResult.then(result => { - onRenderSuccess(result.svg, result.bindFunctions); - }).catch(error => { - // 需求 18.5: 渲染失败时记录错误但不抛出异常 - const retryCode = this.convertLegacySyntax(code); - if (this.shouldRetryWithGraphSyntax(error, code) && retryCode !== code) { - this.logDebug('语法降级后重试渲染'); - this.renderChartLegacy(loadingContainer, retryCode, container, chartId); - return; - } - if (this.tryUpgradeMermaidLibrary(loadingContainer, code)) { - return; - } - this.logError(`图表渲染失败: ${chartId}`, error); - this.handleRenderError(loadingContainer, error, rawCode); - }); - return; - } - - if (renderResult && typeof renderResult === 'object' && typeof renderResult.svg === 'string') { - onRenderSuccess(renderResult.svg, renderResult.bindFunctions); - return; - } - - if (typeof renderResult === 'string') { - onRenderSuccess(renderResult); - return; - } - - this.logError('Mermaid.render 没有返回 Promise,可能是版本不兼容'); - if (this.tryUpgradeMermaidLibrary(loadingContainer, code)) { - return; - } - const legacyCode = this.convertLegacySyntax(code); - this.renderChartLegacy(loadingContainer, legacyCode, container, chartId); - return; - - } catch (error) { - // 需求 18.5: 捕获所有异常,确保不影响其他图表 - this.logError(`渲染异常: ${chartId}`, error); - - // 尝试显示错误信息 - try { - this.handleRenderError(element, error, ''); - } catch (e) { - // 如果错误处理也失败,只记录日志 - this.logError('错误处理失败', e); - } - } - }, - - /** - * 使用旧版 Mermaid API 渲染(兼容 Mermaid 8.x) - * @param {HTMLElement} element - 原始代码块元素 - * @param {string} code - Mermaid 代码 - * @param {HTMLElement} container - 容器元素 - * @param {string} chartId - 图表 ID - */ - renderChartLegacy(element, code, container, chartId) { - try { - this.logDebug('尝试使用旧版 Mermaid API'); - - // Mermaid 8.x 使用 mermaidAPI.render - if (typeof window.mermaidAPI !== 'undefined' && typeof window.mermaidAPI.render === 'function') { - window.mermaidAPI.render(`mermaid-svg-${chartId}`, code, (svgCode) => { - container.innerHTML = svgCode; - container.dataset.mermaidCode = code; - container.dataset.currentTheme = this.getMermaidTheme(); - - // 需求 3.5: 添加渲染状态标记,避免重复渲染 - container.setAttribute('data-mermaid-rendered', 'true'); - - element.parentNode.replaceChild(container, element); - this.rendered.add(container); - this.applyStyles(container); - this.logDebug(`图表渲染成功(旧版 API): ${chartId}`); - }); - } else { - // 最后的降级方案:直接使用 mermaid.init() - container.className = 'mermaid'; - container.textContent = code; - element.parentNode.replaceChild(container, element); - - if (typeof window.mermaid.init === 'function') { - window.mermaid.init(undefined, container); - this.rendered.add(container); - - // 需求 3.5: 添加渲染状态标记,避免重复渲染 - container.setAttribute('data-mermaid-rendered', 'true'); - - this.logDebug(`图表渲染成功(init 方法): ${chartId}`); - } else { - this.handleRenderError(element, new Error('无可用的 Mermaid 渲染方法'), code); - } - } - } catch (error) { - this.logError('旧版 API 渲染失败', error); - if (this.tryUpgradeMermaidLibrary(element, code)) { - return; - } - this.handleRenderError(element, error, code); - } - }, - - /** - * 处理渲染错误 - * @param {HTMLElement} element - 原始代码块元素 - * @param {Error} error - 错误对象 - * @param {string} code - 原始代码 - */ - handleRenderError(element, error, code) { - this.logError('图表渲染失败', error); - - // 如果元素已经是错误容器,避免重复嵌套 - if (element.classList && element.classList.contains('mermaid-error-container')) { - this.logDebug('元素已经是错误容器,跳过重复处理'); - return; - } - - // 提取原始代码(优先使用传入的 code,其次从元素中提取) - let originalCode = code; - if (!originalCode) { - // 尝试从不同类型的元素中提取代码 - if (element.dataset && element.dataset.mermaidCode) { - originalCode = element.dataset.mermaidCode; - } else if (element.textContent) { - originalCode = element.textContent.trim(); - } - } - - // 创建错误提示容器 - const errorContainer = document.createElement('div'); - errorContainer.className = 'mermaid-error-container'; - errorContainer.dataset.errorHandled = 'true'; // 标记已处理 - - // 提取错误信息 - const errorMessage = error.message || '未知错误'; - const errorType = this.getErrorType(errorMessage); - const errorLine = this.extractErrorLine(errorMessage); - - // 创建错误显示结构 - const errorHeader = document.createElement('div'); - errorHeader.className = 'mermaid-error-header'; - errorHeader.innerHTML = ` - ⚠️ - Mermaid 图表渲染失败 - `; - - const errorBody = document.createElement('div'); - errorBody.className = 'mermaid-error-body'; - - let errorBodyHTML = ` -

错误类型: ${errorType}

-

${this.escapeHtml(errorMessage)}

- `; - - // 如果提取到错误行号,显示它 - if (errorLine) { - errorBodyHTML += `

错误位置: 第 ${errorLine} 行

`; - } - - errorBody.innerHTML = errorBodyHTML; - - // 创建代码查看区域 - const codeDetails = document.createElement('details'); - codeDetails.className = 'mermaid-error-code'; - - const codeSummary = document.createElement('summary'); - codeSummary.textContent = '📄 查看原始代码'; - - const codeBlock = document.createElement('pre'); - const codeElement = document.createElement('code'); - codeElement.className = 'language-mermaid'; - codeElement.textContent = originalCode || '(无法提取代码)'; - - codeBlock.appendChild(codeElement); - codeDetails.appendChild(codeSummary); - codeDetails.appendChild(codeBlock); - - // 组装错误容器 - errorContainer.appendChild(errorHeader); - errorContainer.appendChild(errorBody); - errorContainer.appendChild(codeDetails); - - // 替换原始代码块 - if (element.parentNode) { - 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 '格式错误'; - } else if (errorMessage.includes('Unknown')) { - return '未知图表类型'; - } - return '渲染错误'; - }, - - /** - * 从错误信息中提取行号 - * @param {string} errorMessage - 错误信息 - * @returns {string|null} 行号或 null - */ - extractErrorLine(errorMessage) { - // 尝试匹配常见的行号格式 - const linePatterns = [ - /line (\d+)/i, - /at line (\d+)/i, - /on line (\d+)/i, - /第 (\d+) 行/, - ]; - - for (const pattern of linePatterns) { - const match = errorMessage.match(pattern); - if (match && match[1]) { - return match[1]; - } - } - - return null; - }, - - /** - * HTML 转义 - * @param {string} text - 要转义的文本 - * @returns {string} 转义后的文本 - */ - escapeHtml(text) { - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; - }, - - // ---------- 样式增强 ---------- - - /** - * 应用容器样式并添加缩放控制 - * 需求 18.3: 使用淡入动画,提升视觉体验 - * @param {HTMLElement} container - 图表容器 - */ - applyStyles(container) { - // 需求 18.3: 添加淡入动画 - container.style.opacity = '0'; - - // 使用 requestAnimationFrame 确保动画流畅 - requestAnimationFrame(() => { - container.style.transition = 'opacity 0.4s ease-in'; - container.style.opacity = '1'; - - // 动画完成后清理过渡样式 - setTimeout(() => { - container.style.transition = ''; - }, 400); - }); - - // 确保 SVG 响应式 - const svg = container.querySelector('svg'); - if (svg) { - svg.style.width = '100%'; - svg.style.height = 'auto'; - } - - // 添加缩放功能 - this.addZoomControls(container); - }, - - /** - * 添加缩放控制按钮 - * @param {HTMLElement} container - 图表容器 - */ - addZoomControls(container) { - // 创建内部容器包裹 SVG - const svg = container.querySelector('svg'); - if (!svg) return; - - // 创建内部容器 - const inner = document.createElement('div'); - inner.className = 'mermaid-container-inner'; - - // 将 SVG 移到内部容器 - container.appendChild(inner); - inner.appendChild(svg); - - // 创建缩放控制 - // 需求 14.1: 添加全屏按钮 - // 需求 15.1: 添加导出按钮和菜单 - const controls = document.createElement('div'); - controls.className = 'mermaid-zoom-controls'; - controls.innerHTML = ` - - 100% - - - - - `; - - container.appendChild(controls); - - // 需求 15.1: 创建导出菜单 - const exportMenu = document.createElement('div'); - exportMenu.className = 'mermaid-export-menu'; - exportMenu.innerHTML = ` - - - `; - container.appendChild(exportMenu); - - // 创建提示文本 - const hint = document.createElement('div'); - hint.className = 'mermaid-hint'; - hint.textContent = '按住 Ctrl+滚轮缩放 | 拖拽移动'; - container.appendChild(hint); - - // 缩放状态 - let scale = 1; - const minScale = 0.5; - const maxScale = 3; - const step = 0.25; - - // 更新缩放显示 - // 需求 12.2, 12.3: 使用 CSS transform 实现硬件加速和平滑过渡 - // 需求 12.4, 20.3: 更新按钮状态 - const updateZoom = (useTransition = true) => { - // 控制是否使用过渡动画 - if (!useTransition) { - inner.style.transition = 'none'; - } else { - inner.style.transition = 'transform 0.3s ease'; - } - - inner.style.transform = `scale(${scale})`; - controls.querySelector('.mermaid-zoom-level').textContent = Math.round(scale * 100) + '%'; - - // 更新按钮状态 - const zoomInBtn = controls.querySelector('[data-action="zoom-in"]'); - const zoomOutBtn = controls.querySelector('[data-action="zoom-out"]'); - - // 禁用/启用放大按钮 - if (scale >= maxScale) { - zoomInBtn.disabled = true; - zoomInBtn.style.opacity = '0.4'; - zoomInBtn.style.cursor = 'not-allowed'; - } else { - zoomInBtn.disabled = false; - zoomInBtn.style.opacity = '1'; - zoomInBtn.style.cursor = 'pointer'; - } - - // 禁用/启用缩小按钮 - if (scale <= minScale) { - zoomOutBtn.disabled = true; - zoomOutBtn.style.opacity = '0.4'; - zoomOutBtn.style.cursor = 'not-allowed'; - } else { - zoomOutBtn.disabled = false; - zoomOutBtn.style.opacity = '1'; - zoomOutBtn.style.cursor = 'pointer'; - } - - // 恢复过渡(用于下次按钮点击) - if (!useTransition) { - requestAnimationFrame(() => { - inner.style.transition = 'transform 0.3s ease'; - }); - } - }; - - // 绑定按钮事件 - // 需求 14.1, 14.2, 14.3: 全屏按钮功能 - // 需求 15.1: 导出按钮功能 - controls.addEventListener('click', (e) => { - const btn = e.target.closest('.mermaid-zoom-btn'); - if (!btn || btn.disabled) return; // 忽略禁用的按钮 - - const action = btn.dataset.action; - - if (action === 'zoom-in' && scale < maxScale) { - scale = Math.min(scale + step, maxScale); - updateZoom(true); // 按钮点击使用平滑过渡 - } else if (action === 'zoom-out' && scale > minScale) { - scale = Math.max(scale - step, minScale); - updateZoom(true); // 按钮点击使用平滑过渡 - } else if (action === 'zoom-reset') { - scale = 1; - updateZoom(true); // 重置使用平滑过渡 - } else if (action === 'fullscreen') { - // 需求 14.1: 点击全屏按钮 - toggleFullscreen(); - } else if (action === 'export') { - // 需求 15.1: 点击导出按钮,显示/隐藏导出菜单 - toggleExportMenu(); - } - }); - - // ========================================================================== - // 需求 15: Mermaid 导出功能 - // ========================================================================== - - /** - * 切换导出菜单显示/隐藏 - * 需求 15.1: 点击导出按钮显示导出选项 - */ - const toggleExportMenu = () => { - const isVisible = exportMenu.classList.contains('visible'); - if (isVisible) { - exportMenu.classList.remove('visible'); - } else { - exportMenu.classList.add('visible'); - } - }; - - // 点击导出选项 - exportMenu.addEventListener('click', (e) => { - const option = e.target.closest('.mermaid-export-option'); - if (!option) return; - - const format = option.dataset.format; - exportMenu.classList.remove('visible'); - - if (format === 'png') { - exportAsPNG(); - } else if (format === 'svg') { - exportAsSVG(); - } - }); - - // 点击容器外部关闭导出菜单 - document.addEventListener('click', (e) => { - if (!container.contains(e.target)) { - exportMenu.classList.remove('visible'); - } - }); - - /** - * 导出为 PNG - * 需求 15.2: 将图表转换为 PNG 图片并下载 - * 需求 15.4: 保持图表当前的缩放级别和样式 - */ - const exportAsPNG = async () => { - try { - const svgElement = inner.querySelector('svg'); - if (!svgElement) { - throw new Error('未找到 SVG 元素'); - } - - // 克隆 SVG 以避免修改原始元素 - const clonedSvg = svgElement.cloneNode(true); - - // 需求 15.4: 应用当前缩放级别 - const svgWidth = svgElement.getBoundingClientRect().width * scale; - const svgHeight = svgElement.getBoundingClientRect().height * scale; - - clonedSvg.setAttribute('width', svgWidth); - clonedSvg.setAttribute('height', svgHeight); - - // 将 SVG 转换为 data URL - const svgData = new XMLSerializer().serializeToString(clonedSvg); - const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' }); - const svgUrl = URL.createObjectURL(svgBlob); - - // 创建图片元素 - const img = new Image(); - img.onload = () => { - // 创建 canvas - const canvas = document.createElement('canvas'); - canvas.width = svgWidth; - canvas.height = svgHeight; - - const ctx = canvas.getContext('2d'); - - // 设置白色背景(PNG 默认透明) - ctx.fillStyle = '#ffffff'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - // 绘制图片 - ctx.drawImage(img, 0, 0, svgWidth, svgHeight); - - // 转换为 PNG 并下载 - canvas.toBlob((blob) => { - if (blob) { - downloadFile(blob, 'mermaid-chart.png'); - } - URL.revokeObjectURL(svgUrl); - }, 'image/png'); - }; - - // 需求 15.5: 添加导出错误处理 - img.onerror = () => { - URL.revokeObjectURL(svgUrl); - throw new Error('图片加载失败'); - }; - - img.src = svgUrl; - - } catch (error) { - // 需求 15.5: 显示友好的错误提示 - handleExportError('PNG', error); - } - }; - - /** - * 导出为 SVG - * 需求 15.3: 将 SVG 代码保存为文件并下载 - * 需求 15.4: 保持图表当前的缩放级别和样式 - */ - const exportAsSVG = () => { - try { - const svgElement = inner.querySelector('svg'); - if (!svgElement) { - throw new Error('未找到 SVG 元素'); - } - - // 克隆 SVG - const clonedSvg = svgElement.cloneNode(true); - - // 需求 15.4: 应用当前缩放级别 - if (scale !== 1) { - const svgWidth = svgElement.getBoundingClientRect().width * scale; - const svgHeight = svgElement.getBoundingClientRect().height * scale; - - clonedSvg.setAttribute('width', svgWidth); - clonedSvg.setAttribute('height', svgHeight); - } - - // 添加 XML 声明和命名空间 - const svgData = new XMLSerializer().serializeToString(clonedSvg); - const svgWithDeclaration = '\n' + svgData; - - // 创建 Blob 并下载 - const blob = new Blob([svgWithDeclaration], { type: 'image/svg+xml;charset=utf-8' }); - downloadFile(blob, 'mermaid-chart.svg'); - - } catch (error) { - // 需求 15.5: 显示友好的错误提示 - handleExportError('SVG', error); - } - }; - - /** - * 下载文件 - * @param {Blob} blob - 文件内容 - * @param {string} filename - 文件名 - */ - const downloadFile = (blob, filename) => { - const url = URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.download = filename; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(url); - }; - - /** - * 处理导出错误 - * 需求 15.5: 显示友好的错误提示 - * @param {string} format - 导出格式 - * @param {Error} error - 错误对象 - */ - const handleExportError = (format, error) => { - console.error(`导出 ${format} 失败:`, error); - - // 显示错误提示 - const errorMsg = document.createElement('div'); - errorMsg.className = 'mermaid-export-error'; - errorMsg.textContent = `导出 ${format} 失败: ${error.message}`; - container.appendChild(errorMsg); - - // 3秒后自动移除错误提示 - setTimeout(() => { - if (errorMsg.parentNode) { - errorMsg.parentNode.removeChild(errorMsg); - } - }, 3000); - }; - - // 鼠标滚轮缩放(按住 Ctrl) - // 需求 12.1: 以鼠标为中心进行缩放 - container.addEventListener('wheel', (e) => { - if (e.ctrlKey || e.metaKey) { - e.preventDefault(); - - const delta = e.deltaY > 0 ? -step : step; - const newScale = Math.max(minScale, Math.min(maxScale, scale + delta)); - - if (newScale !== scale) { - // 获取鼠标相对于容器的位置 - const rect = container.getBoundingClientRect(); - const mouseX = e.clientX - rect.left; - const mouseY = e.clientY - rect.top; - - // 计算鼠标相对于内容的位置(缩放前) - const scrollLeft = container.scrollLeft; - const scrollTop = container.scrollTop; - const offsetX = (scrollLeft + mouseX) / scale; - const offsetY = (scrollTop + mouseY) / scale; - - // 更新缩放(滚轮缩放不使用过渡动画,更流畅) - scale = newScale; - updateZoom(false); - - // 调整滚动位置,使鼠标位置保持不变 - container.scrollLeft = offsetX * scale - mouseX; - container.scrollTop = offsetY * scale - mouseY; - } - } - }, { passive: false }); - - // 拖拽功能 - let isDragging = false; - let startX = 0; - let startY = 0; - let scrollLeft = 0; - let scrollTop = 0; - let rafId = null; - let pendingX = 0; - let pendingY = 0; - - // 需求 13.5: 智能启用拖拽 - 检查图表是否需要拖拽 - const checkDragEnabled = () => { - const svgElement = inner.querySelector('svg'); - if (!svgElement) return false; - - // 如果图表未缩放且完全可见,禁用拖拽 - const containerRect = container.getBoundingClientRect(); - const svgRect = svgElement.getBoundingClientRect(); - const isFullyVisible = svgRect.width <= containerRect.width && - svgRect.height <= containerRect.height; - const isNotZoomed = Math.abs(scale - 1) < 0.01; - - return !(isFullyVisible && isNotZoomed); - }; - - // 需求 13.3: 使用 requestAnimationFrame 优化拖拽性能 - const updateDragPosition = () => { - if (!isDragging) { - rafId = null; - return; - } - - inner.scrollLeft = scrollLeft - pendingX; - inner.scrollTop = scrollTop - pendingY; - - rafId = requestAnimationFrame(updateDragPosition); - }; - - inner.addEventListener('mousedown', (e) => { - // 只响应左键 - if (e.button !== 0) return; - - // 需求 13.5: 检查是否需要启用拖拽 - if (!checkDragEnabled()) return; - - isDragging = true; - // 需求 13.1: 改变鼠标光标为抓手样式 - inner.classList.add('dragging'); - - startX = e.pageX - inner.offsetLeft; - startY = e.pageY - inner.offsetTop; - scrollLeft = inner.scrollLeft; - scrollTop = inner.scrollTop; - - // 需求 13.2: 禁用文本选择 - e.preventDefault(); - - // 启动 RAF 循环 - if (!rafId) { - rafId = requestAnimationFrame(updateDragPosition); - } - }); - - document.addEventListener('mousemove', (e) => { - if (!isDragging) return; - - e.preventDefault(); - - const x = e.pageX - inner.offsetLeft; - const y = e.pageY - inner.offsetTop; - // 更新待处理的位置,由 RAF 循环处理 - pendingX = (x - startX) * 2; - pendingY = (y - startY) * 2; - }); - - document.addEventListener('mouseup', () => { - if (isDragging) { - isDragging = false; - // 需求 13.4: 恢复正常光标 - inner.classList.remove('dragging'); - // 取消 RAF 循环 - if (rafId) { - cancelAnimationFrame(rafId); - rafId = null; - } - } - }); - - // 防止拖拽时选中文本 - inner.addEventListener('dragstart', (e) => { - e.preventDefault(); - }); - - // 需求 13.2: 禁用文本选择 - inner.addEventListener('selectstart', (e) => { - if (isDragging) { - e.preventDefault(); - } - }); - - // 需求 12.5: 双击智能缩放 - // 双击图表时,如果当前是默认大小则放大到 2 倍,否则重置到 1 倍 - inner.addEventListener('dblclick', (e) => { - e.preventDefault(); - - // 获取双击位置 - const rect = container.getBoundingClientRect(); - const mouseX = e.clientX - rect.left; - const mouseY = e.clientY - rect.top; - - // 计算鼠标相对于内容的位置(缩放前) - const scrollLeft = container.scrollLeft; - const scrollTop = container.scrollTop; - const offsetX = (scrollLeft + mouseX) / scale; - const offsetY = (scrollTop + mouseY) / scale; - - // 智能缩放:如果接近默认大小则放大,否则重置 - const targetScale = Math.abs(scale - 1) < 0.1 ? 2 : 1; - scale = targetScale; - updateZoom(true); // 使用平滑过渡 - - // 调整滚动位置,使双击位置保持在视野中心 - setTimeout(() => { - container.scrollLeft = offsetX * scale - mouseX; - container.scrollTop = offsetY * scale - mouseY; - }, 50); - }); - - // ========================================================================== - // 需求 14: Mermaid 全屏模式 - // ========================================================================== - - // 全屏状态 - let isFullscreen = false; - let savedScale = 1; - let savedScrollLeft = 0; - let savedScrollTop = 0; - - /** - * 切换全屏模式 - * 需求 14.1: 点击全屏按钮将图表全屏显示 - * 需求 14.2, 14.3: 全屏模式下保持缩放和拖拽功能可用 - * 需求 14.5: 退出全屏恢复图表原始状态 - */ - const toggleFullscreen = () => { - if (!isFullscreen) { - // 进入全屏 - enterFullscreen(); - } else { - // 退出全屏 - exitFullscreen(); - } - }; - - /** - * 进入全屏模式 - */ - const enterFullscreen = () => { - // 保存当前状态 - savedScale = scale; - savedScrollLeft = container.scrollLeft; - savedScrollTop = container.scrollTop; - - // 添加全屏类 - container.classList.add('mermaid-fullscreen'); - isFullscreen = true; - - // 更新全屏按钮 - const fullscreenBtn = controls.querySelector('[data-action="fullscreen"]'); - if (fullscreenBtn) { - fullscreenBtn.textContent = '⛶'; - fullscreenBtn.title = '退出全屏'; - fullscreenBtn.classList.add('active'); - } - - // 需求 14.3: 全屏模式下显示退出全屏按钮(已在工具栏中) - // 工具栏在全屏模式下仍然可见 - - // 禁用页面滚动 - document.body.style.overflow = 'hidden'; - - this.logDebug('进入全屏模式'); - }; - - /** - * 退出全屏模式 - * 需求 14.5: 恢复图表原始状态(缩放级别和位置) - */ - const exitFullscreen = () => { - // 移除全屏类 - container.classList.remove('mermaid-fullscreen'); - isFullscreen = false; - - // 更新全屏按钮 - const fullscreenBtn = controls.querySelector('[data-action="fullscreen"]'); - if (fullscreenBtn) { - fullscreenBtn.textContent = '⛶'; - fullscreenBtn.title = '全屏'; - fullscreenBtn.classList.remove('active'); - } - - // 恢复原始状态 - scale = savedScale; - updateZoom(false); - - // 恢复滚动位置 - setTimeout(() => { - container.scrollLeft = savedScrollLeft; - container.scrollTop = savedScrollTop; - }, 50); - - // 恢复页面滚动 - document.body.style.overflow = ''; - - this.logDebug('退出全屏模式'); - }; - - // 需求 14.4: 按 ESC 键退出全屏模式 - const handleEscKey = (e) => { - if (e.key === 'Escape' && isFullscreen) { - exitFullscreen(); - } - }; - - // 绑定 ESC 键事件 - document.addEventListener('keydown', handleEscKey); - - // 清理函数(在容器销毁时调用) - container.dataset.cleanupFullscreen = 'registered'; - container._fullscreenCleanup = () => { - document.removeEventListener('keydown', handleEscKey); - if (isFullscreen) { - exitFullscreen(); - } - }; - - // ========================================================================== - // 需求 16.2-16.4: 移动端触摸手势支持 - // ========================================================================== - - // 触摸状态 - let touchStartDistance = 0; - let touchStartScale = 1; - let touchStartX = 0; - let touchStartY = 0; - let isTouchDragging = false; - let touchScrollLeft = 0; - let touchScrollTop = 0; - - /** - * 计算两个触摸点之间的距离 - * @param {Touch} touch1 - 第一个触摸点 - * @param {Touch} touch2 - 第二个触摸点 - * @returns {number} 距离 - */ - const getTouchDistance = (touch1, touch2) => { - const dx = touch2.clientX - touch1.clientX; - const dy = touch2.clientY - touch1.clientY; - return Math.sqrt(dx * dx + dy * dy); - }; - - /** - * 获取两个触摸点的中心位置 - * @param {Touch} touch1 - 第一个触摸点 - * @param {Touch} touch2 - 第二个触摸点 - * @returns {Object} 中心位置 {x, y} - */ - const getTouchCenter = (touch1, touch2) => { - return { - x: (touch1.clientX + touch2.clientX) / 2, - y: (touch1.clientY + touch2.clientY) / 2 - }; - }; - - // 需求 16.2: 双指缩放手势 - inner.addEventListener('touchstart', (e) => { - if (e.touches.length === 2) { - // 双指触摸 - 缩放模式 - e.preventDefault(); - - touchStartDistance = getTouchDistance(e.touches[0], e.touches[1]); - touchStartScale = scale; - - // 记录缩放中心点 - const center = getTouchCenter(e.touches[0], e.touches[1]); - const rect = container.getBoundingClientRect(); - touchStartX = center.x - rect.left; - touchStartY = center.y - rect.top; - - } else if (e.touches.length === 1) { - // 需求 16.3: 单指拖拽移动 - // 检查是否需要启用拖拽 - if (!checkDragEnabled()) return; - - isTouchDragging = true; - inner.classList.add('dragging'); - - const touch = e.touches[0]; - touchStartX = touch.clientX; - touchStartY = touch.clientY; - touchScrollLeft = container.scrollLeft; - touchScrollTop = container.scrollTop; - } - }, { passive: false }); - - // 需求 16.4: 优化触摸事件响应速度 - inner.addEventListener('touchmove', (e) => { - if (e.touches.length === 2) { - // 双指缩放 - e.preventDefault(); - - const currentDistance = getTouchDistance(e.touches[0], e.touches[1]); - const scaleChange = currentDistance / touchStartDistance; - const newScale = Math.max(minScale, Math.min(maxScale, touchStartScale * scaleChange)); - - if (newScale !== scale) { - // 获取缩放中心点 - const center = getTouchCenter(e.touches[0], e.touches[1]); - const rect = container.getBoundingClientRect(); - const centerX = center.x - rect.left; - const centerY = center.y - rect.top; - - // 计算缩放前的内容位置 - const scrollLeft = container.scrollLeft; - const scrollTop = container.scrollTop; - const offsetX = (scrollLeft + centerX) / scale; - const offsetY = (scrollTop + centerY) / scale; - - // 更新缩放(不使用过渡动画,更流畅) - scale = newScale; - updateZoom(false); - - // 调整滚动位置,使缩放中心保持不变 - requestAnimationFrame(() => { - container.scrollLeft = offsetX * scale - centerX; - container.scrollTop = offsetY * scale - centerY; - }); - } - - } else if (e.touches.length === 1 && isTouchDragging) { - // 单指拖拽 - e.preventDefault(); - - const touch = e.touches[0]; - const deltaX = touch.clientX - touchStartX; - const deltaY = touch.clientY - touchStartY; - - // 使用 requestAnimationFrame 优化性能 - requestAnimationFrame(() => { - container.scrollLeft = touchScrollLeft - deltaX; - container.scrollTop = touchScrollTop - deltaY; - }); - } - }, { passive: false }); - - inner.addEventListener('touchend', (e) => { - if (isTouchDragging) { - isTouchDragging = false; - inner.classList.remove('dragging'); - } - - // 重置触摸状态 - if (e.touches.length === 0) { - touchStartDistance = 0; - touchStartScale = 1; - } - }, { passive: true }); - - inner.addEventListener('touchcancel', () => { - if (isTouchDragging) { - isTouchDragging = false; - inner.classList.remove('dragging'); - } - touchStartDistance = 0; - touchStartScale = 1; - }, { passive: true }); - }, - - // ---------- 主题切换监听 ---------- - - /** - * 监听主题切换事件 - */ - 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('主题切换监听器已启动'); - }, - - /** - * 重新渲染所有图表(主题切换时) - * 需求 17.3: 保持图表的缩放级别和位置 - * 需求 17.4, 17.5: 使用淡入淡出过渡效果 - */ - reRenderCharts() { - // 只选择成功渲染的图表容器,排除错误容器 - const charts = document.querySelectorAll('.mermaid-container:not(.mermaid-error-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) { - this.logDebug('图表缺少原始代码,跳过重新渲染'); - return; - } - - // 检查主题是否真的需要更新 - if (chart.dataset.currentTheme === newTheme) { - this.logDebug('图表主题未改变,跳过重新渲染'); - return; - } - - // 需求 17.3: 保存当前的缩放级别和滚动位置 - const inner = chart.querySelector('.mermaid-container-inner'); - let savedState = null; - - if (inner) { - const transform = window.getComputedStyle(inner).transform; - const scrollLeft = inner.scrollLeft; - const scrollTop = inner.scrollTop; - - // 提取缩放比例 - let scale = 1; - if (transform && transform !== 'none') { - const matrix = transform.match(/matrix\(([^)]+)\)/); - if (matrix) { - const values = matrix[1].split(',').map(v => parseFloat(v.trim())); - scale = values[0]; // scaleX - } - } - - savedState = { - scale: scale, - scrollLeft: scrollLeft, - scrollTop: scrollTop - }; - - this.logDebug('保存图表状态', savedState); - } - - const chartId = `mermaid-chart-rerender-${Date.now()}-${index}`; - - // 需求 17.4: 添加淡出动画 - chart.style.transition = 'opacity 0.2s ease-out'; - chart.style.opacity = '0'; - - // 等待淡出完成后重新渲染 - setTimeout(() => { - window.mermaid.render(`mermaid-svg-${chartId}`, code).then(result => { - // 保存旧的内部容器引用 - const oldInner = chart.querySelector('.mermaid-container-inner'); - - // 更新 SVG 内容 - if (oldInner) { - // 只替换 SVG,保留容器结构 - const oldSvg = oldInner.querySelector('svg'); - if (oldSvg) { - // 创建临时容器解析新的 SVG - const temp = document.createElement('div'); - temp.innerHTML = result.svg; - const newSvg = temp.querySelector('svg'); - - if (newSvg) { - // 替换 SVG - oldSvg.replaceWith(newSvg); - - // 确保 SVG 响应式 - newSvg.style.width = '100%'; - newSvg.style.height = 'auto'; - } - } - } else { - // 如果没有内部容器,直接替换内容 - chart.innerHTML = result.svg; - - // 确保 SVG 响应式 - const svg = chart.querySelector('svg'); - if (svg) { - svg.style.width = '100%'; - svg.style.height = 'auto'; - } - } - - // 更新主题标记 - chart.dataset.currentTheme = newTheme; - - // 需求 17.3: 恢复缩放级别和滚动位置 - if (savedState && oldInner) { - // 恢复缩放 - oldInner.style.transform = `scale(${savedState.scale})`; - - // 恢复滚动位置 - oldInner.scrollLeft = savedState.scrollLeft; - oldInner.scrollTop = savedState.scrollTop; - - // 更新缩放显示 - const zoomLevel = chart.querySelector('.mermaid-zoom-level'); - if (zoomLevel) { - zoomLevel.textContent = Math.round(savedState.scale * 100) + '%'; - } - - this.logDebug('恢复图表状态', savedState); - } - - // 需求 17.5: 添加淡入动画 - requestAnimationFrame(() => { - chart.style.transition = 'opacity 0.3s ease-in'; - chart.style.opacity = '1'; - - // 动画完成后清理过渡样式 - setTimeout(() => { - chart.style.transition = ''; - }, 300); - }); - - this.logDebug(`图表重新渲染成功: ${chartId}`); - }).catch(error => { - this.logError('图表重新渲染失败', error); - - // 重新渲染失败时,恢复显示 - chart.style.opacity = '1'; - chart.style.transition = ''; - - // 不替换为错误容器,保持原样 - // 因为之前已经成功渲染过,只是主题切换失败 - }); - }, 200); // 等待淡出动画完成 - }); - - } 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); - }, - - // ---------- 初始化 ---------- - - /** - * 初始化渲染引擎 - * @returns {Promise} - */ - async init() { - // 检查是否启用 - if (typeof window.argonMermaidConfig !== 'undefined' && !window.argonMermaidConfig.enabled) { - this.logDebug('Mermaid 支持未启用'); - return; - } - - // 需求 2.6, 4.1-4.2: 等待 Mermaid 库加载 - if (!await this.waitForMermaid()) { - this.logError('Mermaid 库加载失败'); - return; - } - - this.initAndRender(); - }, - - /** - * 初始化并渲染 - */ - initAndRender() { - // 初始化配置 - if (!this.initConfig()) { - return; - } - - // 渲染所有图表 - this.renderAllCharts(); - - // 监听主题切换 - this.listenThemeSwitch(); - - this.logDebug('Mermaid 渲染引擎初始化完成'); - } - }; - - // ---------- 启动渲染引擎 ---------- - - // 暴露到全局作用域(用于库加载失败时的降级处理) - window.MermaidRenderer = MermaidRenderer; - - // 在 DOM 加载完成后初始化 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => { - MermaidRenderer.init(); - }); - } else { - // DOM 已加载完成,直接初始化 - MermaidRenderer.init(); - } - - // 暴露到全局(用于 PJAX 等场景) - window.ArgonMermaidRenderer = MermaidRenderer; - -})(); -/* ========== End of Mermaid 图表渲染引擎 ========== */ diff --git a/docs/mermaid-developer-guide.md b/docs/mermaid-developer-guide.md deleted file mode 100644 index 4023ada..0000000 --- a/docs/mermaid-developer-guide.md +++ /dev/null @@ -1,1230 +0,0 @@ -# Mermaid 功能开发者文档 - -## 目录 - -1. [架构概述](#架构概述) -2. [PHP 函数参考](#php-函数参考) -3. [JavaScript API 参考](#javascript-api-参考) -4. [配置系统](#配置系统) -5. [扩展开发](#扩展开发) -6. [测试指南](#测试指南) - ---- - -## 架构概述 - -### 系统组成 - -Mermaid 功能由以下几个部分组成: - -``` -┌─────────────────────────────────────────┐ -│ WordPress 后台设置 │ -│ (settings.php) │ -└──────────────┬──────────────────────────┘ - │ - ├─> 配置管理 (functions.php) - │ ├─ argon_get_mermaid_option() - │ ├─ argon_update_mermaid_option() - │ └─ argon_validate_mermaid_settings() - │ - ├─> 库加载器 (functions.php) - │ ├─ argon_enqueue_mermaid_scripts() - │ ├─ argon_has_mermaid_content() - │ └─ argon_get_mermaid_library_url() - │ - ├─> 插件兼容层 (functions.php) - │ ├─ argon_detect_mermaid_plugins() - │ ├─ argon_is_mermaid_library_enqueued() - │ └─ argon_should_load_mermaid_library() - │ - └─> 前端渲染引擎 (argontheme.js) - ├─ MermaidRenderer.init() - ├─ MermaidRenderer.detectMermaidBlocks() - ├─ MermaidRenderer.renderChart() - └─ MermaidRenderer.handleThemeSwitch() -``` - -### 工作流程 - -```mermaid -flowchart TD - A[页面加载] --> B{检查是否启用} - B -->|否| Z[结束] - B -->|是| C[检测页面内容] - C --> D{包含 Mermaid?} - D -->|否| Z - D -->|是| E[检测插件] - E --> F{插件已加载?} - F -->|是| G[只提供样式] - F -->|否| H[加载 Mermaid 库] - H --> I[初始化配置] - G --> I - I --> J[检测代码块] - J --> K[渲染图表] - K --> L[监听主题切换] - L --> Z -``` - ---- - -## PHP 函数参考 - -### 配置管理函数 - -#### argon_get_mermaid_option() - -获取 Mermaid 配置选项。 - -**函数签名:** -```php -function argon_get_mermaid_option($option_name, $default = null) -``` - -**参数:** -- `$option_name` (string) - 配置选项名称,支持简短名称或完整名称 -- `$default` (mixed) - 默认值,当选项不存在时返回 - -**返回值:** -- (mixed) 配置选项值 - -**支持的选项名称:** -| 简短名称 | 完整名称 | 说明 | -|---------|---------|------| -| `enabled` | `argon_enable_mermaid` | 是否启用 Mermaid | -| `cdn_source` | `argon_mermaid_cdn_source` | CDN 来源 | -| `custom_cdn_url` | `argon_mermaid_cdn_custom_url` | 自定义 CDN URL | -| `theme` | `argon_mermaid_theme` | 图表主题 | -| `use_local` | `argon_mermaid_use_local` | 使用本地镜像 | -| `debug_mode` | `argon_mermaid_debug_mode` | 调试模式 | - -**示例:** -```php -// 使用简短名称 -$enabled = argon_get_mermaid_option('enabled', false); - -// 使用完整名称 -$theme = argon_get_mermaid_option('argon_mermaid_theme', 'auto'); - -// 获取不存在的选项,返回默认值 -$custom = argon_get_mermaid_option('custom_option', 'default_value'); -``` - ---- - -#### argon_update_mermaid_option() - -保存 Mermaid 配置选项。 - -**函数签名:** -```php -function argon_update_mermaid_option($option_name, $value) -``` - -**参数:** -- `$option_name` (string) - 配置选项名称 -- `$value` (mixed) - 配置选项值 - -**返回值:** -- (bool) 是否保存成功 - -**示例:** -```php -// 启用 Mermaid -argon_update_mermaid_option('enabled', true); - -// 设置主题 -argon_update_mermaid_option('theme', 'dark'); - -// 设置 CDN 来源 -argon_update_mermaid_option('cdn_source', 'jsdelivr'); -``` - ---- - -#### argon_validate_mermaid_cdn_url() - -验证 Mermaid CDN URL 格式。 - -**函数签名:** -```php -function argon_validate_mermaid_cdn_url($url) -``` - -**参数:** -- `$url` (string) - CDN URL - -**返回值:** -- (bool) 是否为有效的 CDN URL - -**验证规则:** -1. URL 不能为空 -2. 必须是有效的 URL 格式 -3. 必须以 `.js` 结尾 -4. 必须使用 `http://` 或 `https://` 协议 - -**示例:** -```php -// 有效的 URL -$valid = argon_validate_mermaid_cdn_url('https://cdn.example.com/mermaid.min.js'); -// 返回: true - -// 无效的 URL(不以 .js 结尾) -$invalid = argon_validate_mermaid_cdn_url('https://cdn.example.com/mermaid'); -// 返回: false -``` - ---- - -#### argon_get_mermaid_theme() - -获取当前主题模式对应的 Mermaid 主题。 - -**函数签名:** -```php -function argon_get_mermaid_theme() -``` - -**返回值:** -- (string) Mermaid 主题名称 - -**说明:** -- 如果配置为固定主题,直接返回配置的主题 -- 如果配置为 `auto`,在 PHP 端返回 `default`,实际切换在 JavaScript 中实现 - -**示例:** -```php -$theme = argon_get_mermaid_theme(); -// 返回: 'default', 'dark', 'forest', 'neutral' 之一 -``` - ---- - -### 库加载函数 - -#### argon_has_mermaid_content() - -检测页面内容是否包含 Mermaid 代码块。 - -**函数签名:** -```php -function argon_has_mermaid_content($content) -``` - -**参数:** -- `$content` (string) - 页面内容 - -**返回值:** -- (bool) 是否包含 Mermaid 代码块 - -**支持的格式:** -- `
` -- `
`
-- `
`
-- ``
-
-**示例:**
-```php
-global $post;
-if (argon_has_mermaid_content($post->post_content)) {
-    // 页面包含 Mermaid 代码块
-    echo '检测到 Mermaid 图表';
-}
-```
-
----
-
-#### argon_get_mermaid_library_url()
-
-获取 Mermaid 库的 URL。
-
-**函数签名:**
-```php
-function argon_get_mermaid_library_url()
-```
-
-**返回值:**
-- (string) Mermaid 库 URL
-
-**说明:**
-- 根据配置返回对应的 CDN 或本地路径
-- 如果自定义 URL 无效,自动降级到 jsDelivr
-
-**示例:**
-```php
-$url = argon_get_mermaid_library_url();
-// 返回: 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js'
-```
-
----
-
-#### argon_enqueue_mermaid_scripts()
-
-加载 Mermaid JavaScript 库。
-
-**函数签名:**
-```php
-function argon_enqueue_mermaid_scripts()
-```
-
-**说明:**
-- 在 `wp_enqueue_scripts` 钩子中调用
-- 只在启用 Mermaid 且页面包含代码块时加载
-- 自动检测插件,避免重复加载
-
-**钩子:**
-```php
-add_action('wp_enqueue_scripts', 'argon_enqueue_mermaid_scripts');
-```
-
----
-
-### 插件兼容函数
-
-#### argon_detect_mermaid_plugins()
-
-检测已安装的 Mermaid 相关插件。
-
-**函数签名:**
-```php
-function argon_detect_mermaid_plugins()
-```
-
-**返回值:**
-- (array) 插件检测结果数组
-
-**返回格式:**
-```php
-[
-    'wp-githuber-md' => false,
-    'markdown-block' => false,
-    'code-syntax-block' => false,
-    'mermaid-loaded' => false
-]
-```
-
-**示例:**
-```php
-$plugins = argon_detect_mermaid_plugins();
-if ($plugins['wp-githuber-md']) {
-    echo 'WP Githuber MD 插件已安装';
-}
-```
-
----
-
-#### argon_is_mermaid_library_enqueued()
-
-检查是否有插件已经加载了 Mermaid 库。
-
-**函数签名:**
-```php
-function argon_is_mermaid_library_enqueued()
-```
-
-**返回值:**
-- (bool) 是否已加载
-
-**说明:**
-- 检测常见的 Mermaid 脚本句柄
-- 包括:`mermaid`, `mermaid-js`, `githuber-mermaid`, `wp-mermaid`, `markdown-mermaid`
-
-**示例:**
-```php
-if (argon_is_mermaid_library_enqueued()) {
-    echo 'Mermaid 库已由其他来源加载';
-}
-```
-
----
-
-#### argon_should_load_mermaid_library()
-
-判断是否应该加载 Mermaid 库。
-
-**函数签名:**
-```php
-function argon_should_load_mermaid_library()
-```
-
-**返回值:**
-- (bool) 是否应该加载
-
-**说明:**
-- 检测插件和已加载的库
-- 避免重复加载
-- 在调试模式下输出日志
-
-**示例:**
-```php
-if (argon_should_load_mermaid_library()) {
-    // 主题负责加载 Mermaid 库
-    wp_enqueue_script('mermaid', $url);
-}
-```
-
----
-
-## JavaScript API 参考
-
-### MermaidRenderer 对象
-
-前端渲染引擎,负责检测和渲染 Mermaid 图表。
-
-#### 属性
-
-| 属性 | 类型 | 说明 |
-|------|------|------|
-| `initialized` | boolean | 是否已初始化 |
-| `rendered` | Set | 已渲染的图表 ID 集合 |
-| `config` | Object | 配置对象 |
-
----
-
-#### init()
-
-初始化 Mermaid 配置并渲染所有图表。
-
-**函数签名:**
-```javascript
-MermaidRenderer.init()
-```
-
-**返回值:**
-- (boolean) 是否初始化成功
-
-**说明:**
-- 检查 Mermaid 库是否已加载
-- 初始化 Mermaid 配置
-- 渲染所有图表
-- 监听主题切换事件
-
-**示例:**
-```javascript
-// 页面加载完成后初始化
-document.addEventListener('DOMContentLoaded', function() {
-    if (typeof MermaidRenderer !== 'undefined') {
-        MermaidRenderer.init();
-    }
-});
-```
-
----
-
-#### detectMermaidBlocks()
-
-检测所有 Mermaid 代码块。
-
-**函数签名:**
-```javascript
-MermaidRenderer.detectMermaidBlocks()
-```
-
-**返回值:**
-- (Array) Mermaid 代码块元素数组
-
-**检测规则:**
-1. `div.mermaid` - 标准格式
-2. `pre code.language-mermaid` - Markdown 格式
-3. `pre[data-lang="mermaid"]` - 自定义属性格式
-4. `code.mermaid` - 简化格式
-
-**示例:**
-```javascript
-const blocks = MermaidRenderer.detectMermaidBlocks();
-console.log(`检测到 ${blocks.length} 个 Mermaid 代码块`);
-```
-
----
-
-#### extractMermaidCode()
-
-提取代码块内容。
-
-**函数签名:**
-```javascript
-MermaidRenderer.extractMermaidCode(element)
-```
-
-**参数:**
-- `element` (HTMLElement) - 代码块元素
-
-**返回值:**
-- (string) Mermaid 代码
-
-**说明:**
-- 支持多种代码块格式
-- 自动处理 WP-Markdown 生成的 `document.write()` 格式
-- 解码转义字符(`\n`, `\"`, `\'`)
-
-**示例:**
-```javascript
-const element = document.querySelector('.mermaid');
-const code = MermaidRenderer.extractMermaidCode(element);
-console.log(code);
-```
-
----
-
-#### renderChart()
-
-渲染单个图表。
-
-**函数签名:**
-```javascript
-MermaidRenderer.renderChart(element, index)
-```
-
-**参数:**
-- `element` (HTMLElement) - 代码块元素
-- `index` (number) - 图表索引
-
-**说明:**
-- 生成唯一的图表 ID
-- 避免重复渲染
-- 渲染成功后替换原始代码块
-- 渲染失败时显示错误提示
-
-**示例:**
-```javascript
-const blocks = MermaidRenderer.detectMermaidBlocks();
-blocks.forEach((block, index) => {
-    MermaidRenderer.renderChart(block, index);
-});
-```
-
----
-
-#### handleThemeSwitch()
-
-处理主题切换事件。
-
-**函数签名:**
-```javascript
-MermaidRenderer.handleThemeSwitch()
-```
-
-**说明:**
-- 检测页面主题变化
-- 重新渲染所有图表
-- 使用新的主题配置
-
-**示例:**
-```javascript
-// 监听主题切换
-document.addEventListener('themeChanged', function() {
-    MermaidRenderer.handleThemeSwitch();
-});
-```
-
----
-
-### 配置对象
-
-#### argonMermaidConfig
-
-全局配置对象,由 PHP 传递到前端。
-
-**属性:**
-```javascript
-{
-    enabled: true,                    // 是否启用
-    theme: 'auto',                    // 图表主题
-    debugMode: false,                 // 调试模式
-    fallbackUrls: [],                 // 备用 CDN URL 列表
-    libraryLoadedByPlugin: false      // 库是否由插件加载
-}
-```
-
-**访问方式:**
-```javascript
-if (window.argonMermaidConfig) {
-    console.log('Mermaid 已启用:', argonMermaidConfig.enabled);
-    console.log('当前主题:', argonMermaidConfig.theme);
-}
-```
-
----
-
-## 配置系统
-
-### 配置选项
-
-| 选项名称 | 类型 | 默认值 | 说明 |
-|---------|------|--------|------|
-| `argon_enable_mermaid` | boolean | false | 是否启用 Mermaid |
-| `argon_mermaid_cdn_source` | string | 'jsdelivr' | CDN 来源 |
-| `argon_mermaid_cdn_custom_url` | string | '' | 自定义 CDN URL |
-| `argon_mermaid_theme` | string | 'auto' | 图表主题 |
-| `argon_mermaid_use_local` | boolean | false | 使用本地镜像 |
-| `argon_mermaid_debug_mode` | boolean | false | 调试模式 |
-
-### CDN 来源选项
-
-| 值 | 说明 | URL |
-|----|------|-----|
-| `jsdelivr` | jsDelivr CDN | `https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js` |
-| `unpkg` | unpkg CDN | `https://unpkg.com/mermaid@10/dist/mermaid.min.js` |
-| `custom` | 自定义 CDN | 用户指定的 URL |
-| `local` | 本地文件 | `{theme_url}/assets/vendor/mermaid/mermaid.min.js` |
-
-### 主题选项
-
-| 值 | 说明 |
-|----|------|
-| `auto` | 自动切换(跟随页面主题) |
-| `default` | 默认主题(浅色) |
-| `dark` | 深色主题 |
-| `forest` | 森林主题(绿色) |
-| `neutral` | 中性主题(灰色) |
-
----
-
-## 扩展开发
-
-### 添加新的图表类型支持
-
-Mermaid 支持的所有图表类型都已自动支持,无需额外配置。
-
-### 自定义样式
-
-在 `style.css` 中添加自定义样式:
-
-```css
-/* 自定义 Mermaid 容器样式 */
-.mermaid-container {
-    background: #f5f5f5;
-    padding: 20px;
-    border-radius: 8px;
-    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
-}
-
-/* 自定义错误提示样式 */
-.mermaid-error-container {
-    background: #fff3cd;
-    border-left: 4px solid #ffc107;
-    padding: 15px;
-    border-radius: 4px;
-}
-
-/* 夜间模式样式 */
-html.darkmode .mermaid-container {
-    background: #2d2d2d;
-}
-```
-
-### 添加自定义钩子
-
-在 `functions.php` 中添加钩子:
-
-```php
-/**
- * Mermaid 渲染前钩子
- * 
- * @param string $content 页面内容
- * @return string 修改后的内容
- */
-function my_custom_mermaid_before_render($content) {
-    // 自定义处理逻辑
-    return $content;
-}
-add_filter('argon_mermaid_before_render', 'my_custom_mermaid_before_render');
-
-/**
- * Mermaid 渲染后钩子
- * 
- * @param string $content 渲染后的内容
- * @return string 修改后的内容
- */
-function my_custom_mermaid_after_render($content) {
-    // 自定义处理逻辑
-    return $content;
-}
-add_filter('argon_mermaid_after_render', 'my_custom_mermaid_after_render');
-```
-
----
-
-## 测试指南
-
-### 单元测试
-
-运行 PHP 单元测试:
-
-```bash
-cd tests
-php run-mermaid-config-tests.php
-```
-
-### 测试用例
-
-测试文件位于 `tests/` 目录:
-
-- `test-mermaid-config.php` - 配置管理测试
-- `test-mermaid-loader.php` - 库加载测试
-- `test-mermaid-fallback.php` - CDN 降级测试
-- `test-wp-markdown-format.html` - WP-Markdown 格式测试
-
-### 手动测试
-
-1. **基础功能测试**
-   - 启用 Mermaid 支持
-   - 创建包含 Mermaid 代码的文章
-   - 验证图表是否正确渲染
-
-2. **主题切换测试**
-   - 切换页面主题(日间/夜间)
-   - 验证图表主题是否自动切换
-
-3. **CDN 降级测试**
-   - 模拟 CDN 加载失败
-   - 验证是否自动尝试备用 CDN
-
-4. **插件兼容测试**
-   - 安装 Mermaid 相关插件
-   - 验证是否正确检测并避免冲突
-
-5. **错误处理测试**
-   - 输入错误的 Mermaid 代码
-   - 验证错误提示是否友好
-
----
-
-## 调试技巧
-
-### 启用调试模式
-
-在主题设置中启用调试模式,查看详细日志:
-
-```javascript
-[Argon Mermaid] Mermaid 配置初始化成功 {theme: "default"}
-[Argon Mermaid] 检测到 3 个 Mermaid 代码块
-[Argon Mermaid] 渲染图表 #mermaid-chart-1234567890-0
-[Argon Mermaid] 图表渲染成功
-```
-
-### 浏览器控制台
-
-打开浏览器开发者工具(F12),查看:
-
-1. **控制台日志** - 查看 Mermaid 相关日志
-2. **网络请求** - 查看 CDN 加载状态
-3. **元素检查** - 查看渲染后的 SVG 结构
-
-### PHP 错误日志
-
-在 `wp-config.php` 中启用调试:
-
-```php
-define('WP_DEBUG', true);
-define('WP_DEBUG_LOG', true);
-define('WP_DEBUG_DISPLAY', false);
-```
-
-查看日志文件:`wp-content/debug.log`
-
----
-
-## 性能优化
-
-### 按需加载
-
-主题已实现按需加载,只在包含 Mermaid 代码的页面加载库。
-
-### 异步加载
-
-Mermaid 库使用 `async` 属性异步加载,不阻塞页面渲染。
-
-### CDN 缓存
-
-使用 CDN 加速加载,浏览器会自动缓存库文件。
-
-### 避免重复渲染
-
-使用 `rendered` Set 记录已渲染的图表,避免重复渲染。
-
----
-
-## 贡献指南
-
-### 代码规范
-
-遵循 Argon 主题的代码规范:
-
-- **PHP**: 遵循 WordPress Coding Standards
-- **JavaScript**: 使用 Tab 缩进,严格相等运算符
-- **CSS**: 使用 Tab 缩进,属性独占一行
-
-### 提交规范
-
-- **功能添加**: `feat: 添加 XXX 功能`
-- **Bug 修复**: `fix: 修复 XXX 问题`
-- **文档更新**: `docs: 更新 XXX 文档`
-
-### 测试要求
-
-- 添加新功能时,必须添加对应的测试用例
-- 修复 Bug 时,必须添加回归测试
-- 所有测试必须通过
-
----
-
-## 相关资源
-
-- [Mermaid 官方文档](https://mermaid.js.org/)
-- [WordPress 开发文档](https://developer.wordpress.org/)
-- [Argon 主题 GitHub](https://github.com/solstice23/argon-theme)
-
----
-
-**最后更新:** 2024-01-22  
-**文档版本:** 1.0.0
-
-
----
-
-## 代码块魔改技术
-
-### 概述
-
-代码块魔改是一种在代码高亮之前拦截并转换 Mermaid 代码块的技术,使得标准 Markdown 代码块 (` ```mermaid `) 能够正确渲染为 Mermaid 图表,而不被代码高亮处理。
-
-### 实现原理
-
-#### 1. 执行顺序
-
-```
-页面加载/PJAX切换
-    ↓
-highlightJsRender() 调用
-    ↓
-convertMermaidCodeblocks() 执行 ← 【拦截阶段】
-    ↓
-代码高亮处理其他代码块
-    ↓
-MermaidRenderer.detectMermaidBlocks() ← 【检测阶段】
-    ↓
-MermaidRenderer.renderChart() ← 【渲染阶段】
-```
-
-#### 2. 核心函数
-
-**convertMermaidCodeblocks()**
-
-在代码高亮之前转换 Mermaid 代码块。
-
-**位置:** `argontheme.js` 第 3942 行之前
-
-**功能:**
-- 使用多个选择器查找 mermaid 代码块
-- 提取纯文本代码(使用 `textContent`)
-- 创建 `
` 容器 -- 替换原始代码块元素 -- 添加 `data-processed="true"` 标记防止重复处理 - -**代码示例:** -```javascript -function convertMermaidCodeblocks(){ - // 支持多种代码块格式 - const selectors = [ - 'pre > code.language-mermaid', // 标准 Markdown 格式(最常见) - 'pre > code.mermaid', // 简化格式 - 'code.language-mermaid', // 无 pre 包裹 - 'pre[data-lang="mermaid"]' // 自定义属性格式 - ]; - - let processedCount = 0; - - selectors.forEach(selector => { - document.querySelectorAll(selector).forEach(element => { - // 避免重复处理 - if (element.dataset.mermaidProcessed) { - return; - } - - // 提取代码 - let code = element.textContent.trim(); - if (!code) { - return; - } - - // 创建容器 - const container = document.createElement('div'); - container.className = 'mermaid-from-codeblock'; - container.textContent = code; - container.dataset.processed = 'true'; - - // 替换元素 - const targetElement = element.closest('pre') || element; - if (targetElement.parentNode) { - targetElement.parentNode.replaceChild(container, targetElement); - processedCount++; - } - - // 标记已处理 - element.dataset.mermaidProcessed = 'true'; - }); - }); - - if (processedCount > 0 && typeof console !== 'undefined' && console.log) { - console.log('[Mermaid] 转换了 ' + processedCount + ' 个代码块'); - } -} -``` - -#### 3. 集成点 - -**集成点 1:highlightJsRender() 函数** - -在函数开始处调用 `convertMermaidCodeblocks()`: - -```javascript -function highlightJsRender(){ - // 在代码高亮之前,先处理 Mermaid 代码块 - convertMermaidCodeblocks(); - - if (typeof(hljs) == "undefined"){ - return; - } - // ... 原有的代码高亮逻辑 -} -``` - -**集成点 2:detectMermaidBlocks() 函数** - -在 selectors 数组中添加新的容器类型: - -```javascript -const selectors = [ - 'div.mermaid-shortcode', // Shortcode 格式(推荐) - 'div.mermaid-from-codeblock', // 代码块魔改格式(新增) - 'div.mermaid', // 标准格式 - 'pre code.language-mermaid', // Markdown 格式(降级) - 'pre[data-lang="mermaid"]', // 自定义属性格式 - 'code.mermaid' // 简化格式 -]; -``` - -**集成点 3:extractMermaidCode() 函数** - -添加对新容器类型的处理: - -```javascript -// 处理代码块魔改格式(新增) -else if (element.classList.contains('mermaid-from-codeblock')) { - code = element.textContent; - this.logDebug('从代码块魔改格式提取代码'); -} -``` - -### 技术细节 - -#### 选择器优先级 - -选择器按以下优先级匹配: - -1. `pre > code.language-mermaid` - 标准 Markdown 格式(最常见) -2. `pre > code.mermaid` - 简化格式 -3. `code.language-mermaid` - 无 pre 包裹 -4. `pre[data-lang="mermaid"]` - 自定义属性格式 - -#### 重复处理防护 - -使用 `data-mermaid-processed` 属性标记已处理的元素: - -```javascript -if (element.dataset.mermaidProcessed) { - return; // 跳过已处理的元素 -} -// ... 处理逻辑 -element.dataset.mermaidProcessed = 'true'; -``` - -这样可以: -- 避免 PJAX 切换时重复处理 -- 防止多次调用导致的错误 -- 提高性能 - -#### 代码提取 - -使用 `textContent` 而非 `innerHTML`: - -```javascript -let code = element.textContent.trim(); -``` - -**优势:** -- 获取纯文本,避免 HTML 实体 -- 防止 XSS 攻击 -- 保持原始内容不变 - -#### 容器创建 - -创建新的 div 容器: - -```javascript -const container = document.createElement('div'); -container.className = 'mermaid-from-codeblock'; -container.textContent = code; -container.dataset.processed = 'true'; -``` - -**属性说明:** -- `class="mermaid-from-codeblock"` - 标识来源于代码块 -- `data-processed="true"` - 标记已处理 -- `textContent` - 纯文本内容(不使用 `innerHTML`) - -#### 元素替换 - -优先替换整个 `
` 元素:
-
-```javascript
-const targetElement = element.closest('pre') || element;
-if (targetElement.parentNode) {
-	targetElement.parentNode.replaceChild(container, targetElement);
-}
-```
-
-**逻辑:**
-- 如果代码块在 `
` 中,替换整个 `
` 元素
-- 如果没有 `
` 包裹,替换 `` 元素
-- 保留原始位置和上下文
-
-### PJAX 兼容性
-
-代码块转换自动支持 PJAX,因为:
-
-1. `highlightJsRender()` 已在 PJAX 回调中调用
-2. `convertMermaidCodeblocks()` 在 `highlightJsRender()` 开始处执行
-3. 使用 `data-processed` 属性防止重复处理
-
-**PJAX 回调链:**
-```javascript
-$(document).on('pjax:complete', function() {
-	// ... 其他初始化
-	try { highlightJsRender(); } catch (err) { ... }  // 包含代码块转换
-	// ... 其他初始化
-});
-```
-
-### 性能优化
-
-#### 1. 使用原生 JavaScript
-
-```javascript
-document.querySelectorAll(selector)  // 而非 $(selector)
-```
-
-**理由:** 原生方法性能更好,减少 jQuery 开销
-
-#### 2. 提前返回
-
-```javascript
-if (element.dataset.mermaidProcessed) {
-	return;  // 提前返回,避免不必要的处理
-}
-```
-
-**理由:** 减少重复处理,提高效率
-
-#### 3. 批量处理
-
-```javascript
-selectors.forEach(selector => {
-	document.querySelectorAll(selector).forEach(element => {
-		// 处理逻辑
-	});
-});
-```
-
-**理由:** 一次性查找所有元素,减少 DOM 查询次数
-
-#### 4. 最小化 DOM 操作
-
-```javascript
-const container = document.createElement('div');
-container.className = 'mermaid-from-codeblock';
-container.textContent = code;
-container.dataset.processed = 'true';
-targetElement.parentNode.replaceChild(container, targetElement);
-```
-
-**理由:** 一次性创建和替换,减少重排和重绘
-
-### 安全性
-
-#### XSS 防护
-
-使用 `textContent` 而非 `innerHTML`:
-
-```javascript
-container.textContent = code;  // 安全
-// container.innerHTML = code;  // 不安全
-```
-
-**理由:** `textContent` 会自动转义 HTML,防止 XSS 攻击
-
-#### 代码来源验证
-
-```javascript
-let code = element.textContent.trim();
-if (!code) {
-	return;  // 拒绝空代码
-}
-```
-
-**理由:** 避免处理恶意或无效的代码块
-
-### 错误处理
-
-#### 空代码检查
-
-```javascript
-let code = element.textContent.trim();
-if (!code) {
-	return; // 跳过空代码块
-}
-```
-
-**理由:** 避免创建空容器,减少不必要的 DOM 操作
-
-#### Try-Catch 包裹
-
-```javascript
-try {
-	convertMermaidCodeblocks();
-} catch (err) {
-	console.error('Mermaid 代码块转换失败:', err);
-}
-```
-
-**理由:** 捕获异常,不中断其他代码块的处理
-
-#### 降级支持
-
-如果代码块转换失败,仍可通过降级选择器检测:
-
-```javascript
-'pre code.language-mermaid'  // 降级选择器
-```
-
-**理由:** 确保即使转换失败,仍能渲染
-
-### 扩展方式
-
-#### 添加新的选择器
-
-在 `selectors` 数组中添加新的选择器:
-
-```javascript
-const selectors = [
-	'pre > code.language-mermaid',
-	'pre > code.mermaid',
-	'code.language-mermaid',
-	'pre[data-lang="mermaid"]',
-	'your-custom-selector'  // 添加自定义选择器
-];
-```
-
-#### 自定义容器类名
-
-修改容器类名:
-
-```javascript
-container.className = 'your-custom-class';
-```
-
-然后在 `detectMermaidBlocks()` 中添加对应的选择器:
-
-```javascript
-const selectors = [
-	'div.mermaid-shortcode',
-	'div.your-custom-class',  // 添加自定义类名
-	'div.mermaid',
-	// ...
-];
-```
-
-### 调试技巧
-
-#### 1. 启用调试日志
-
-在 `convertMermaidCodeblocks()` 中添加日志:
-
-```javascript
-console.log('[Mermaid] 找到代码块:', element);
-console.log('[Mermaid] 提取的代码:', code);
-console.log('[Mermaid] 创建的容器:', container);
-```
-
-#### 2. 检查转换结果
-
-在浏览器控制台中检查:
-
-```javascript
-// 查看所有转换后的容器
-document.querySelectorAll('.mermaid-from-codeblock')
-
-// 查看容器内容
-document.querySelectorAll('.mermaid-from-codeblock').forEach(el => {
-	console.log(el.textContent);
-});
-```
-
-#### 3. 验证执行顺序
-
-在关键位置添加日志:
-
-```javascript
-function highlightJsRender(){
-	console.log('[1] highlightJsRender 开始');
-	convertMermaidCodeblocks();
-	console.log('[2] convertMermaidCodeblocks 完成');
-	// ... 代码高亮逻辑
-	console.log('[3] 代码高亮完成');
-}
-```
-
-### 测试用例
-
-参见 `tests/test-codeblock-magic.html` 文件,包含以下测试:
-
-1. 标准 Markdown 格式
-2. 多个代码块(批量处理)
-3. 特殊字符保留
-4. 空代码块(边界情况)
-5. 多行代码块(换行符保留)
-6. 简化格式
-7. 无 pre 包裹
-8. 自定义属性格式
-9. 复杂图表
-
-### 常见问题
-
-#### Q: 为什么要在代码高亮之前拦截?
-
-A: 因为代码高亮会:
-- 添加行号和控制按钮
-- 转换特殊字符(`-->` → `–>`)
-- 添加额外的 HTML 结构
-- 干扰 Mermaid 渲染
-
-#### Q: 为什么使用 `textContent` 而不是 `innerHTML`?
-
-A: 因为:
-- `textContent` 获取纯文本,避免 HTML 实体
-- 防止 XSS 攻击
-- 保持原始内容不变
-
-#### Q: 如何确保 PJAX 兼容性?
-
-A: 通过:
-- 在 `highlightJsRender()` 中调用(已在 PJAX 回调中)
-- 使用 `data-processed` 属性防止重复处理
-- 无需额外修改 PJAX 逻辑
-
-#### Q: 如何添加新的代码块格式?
-
-A: 在 `selectors` 数组中添加新的选择器,然后在 `detectMermaidBlocks()` 和 `extractMermaidCode()` 中添加对应的处理逻辑。
-
----
diff --git a/docs/mermaid-faq.md b/docs/mermaid-faq.md
deleted file mode 100644
index 6e67c4e..0000000
--- a/docs/mermaid-faq.md
+++ /dev/null
@@ -1,712 +0,0 @@
-# Mermaid 图表常见问题解答 (FAQ)
-
-## 目录
-
-- [基础问题](#基础问题)
-- [配置问题](#配置问题)
-- [渲染问题](#渲染问题)
-- [兼容性问题](#兼容性问题)
-- [性能问题](#性能问题)
-- [高级问题](#高级问题)
-
----
-
-## 基础问题
-
-### Q1: 什么是 Mermaid?
-
-**A:** Mermaid 是一个基于 JavaScript 的图表和图形工具,它使用类似 Markdown 的文本语法来创建和修改图表。您可以用简单的文本代码创建流程图、时序图、类图等多种专业图表。
-
-**优势:**
-- 📝 文本即代码,易于版本控制
-- 🎨 自动布局,无需手动调整
-- 🔄 易于修改和维护
-- 📱 响应式设计,自适应屏幕
-
----
-
-### Q2: Argon 主题支持哪些 Mermaid 图表类型?
-
-**A:** Argon 主题支持 Mermaid 的所有主要图表类型:
-
-1. **流程图 (Flowchart)** - 展示流程和决策
-2. **时序图 (Sequence Diagram)** - 描述对象交互
-3. **类图 (Class Diagram)** - 展示类结构
-4. **状态图 (State Diagram)** - 表示状态转换
-5. **饼图 (Pie Chart)** - 显示数据占比
-6. **甘特图 (Gantt Chart)** - 项目进度管理
-7. **用户旅程图 (User Journey)** - 用户体验流程
-8. **Git 图 (Git Graph)** - 版本控制可视化
-
-详细示例请参考[用户指南](mermaid-user-guide.md#支持的图表类型)。
-
----
-
-### Q3: 如何在文章中插入 Mermaid 图表?
-
-**A:** 有三种方式:
-
-**方式 1:标准 Markdown 代码块**(推荐)⭐
-````markdown
-三个反引号mermaid
-flowchart TD
-    A[开始] --> B[结束]
-三个反引号
-````
-
-**优点:**
-- ✅ 符合标准 Markdown 语法
-- ✅ 在所有 Markdown 编辑器中都能正确显示
-- ✅ 易于迁移到其他平台(GitHub、GitLab、Typora 等)
-- ✅ 主题自动拦截处理,避免代码高亮干扰
-
-**方式 2:Markdown 容器语法**(备选)
-````markdown
-::: mermaid
-flowchart TD
-    A[开始] --> B[结束]
-:::
-````
-
-**优点:**
-- ✅ 符合 Markdown 扩展规范(VuePress、Docusaurus 等)
-- ✅ 不会被 WP-Markdown 当作代码块处理
-
-**方式 3:Shortcode 格式**(兼容)
-```
-[mermaid]
-flowchart TD
-    A[开始] --> B[结束]
-[/mermaid]
-```
-
-**优点:**
-- ✅ WordPress 原生支持
-- ✅ 兼容性最好
-
-**缺点:**
-- ❌ 不符合 Markdown 标准
-- ❌ 在其他平台无法使用
-
----
-
-### Q4: 为什么推荐使用标准 Markdown 代码块?
-
-**A:** 因为标准 Markdown 代码块 (` ```mermaid `) 是最通用的方式:
-
-- **GitHub** 使用这种语法
-- **GitLab** 使用这种语法
-- **Typora** 使用这种语法
-- **VS Code** 使用这种语法
-- **符合 CommonMark 规范**
-
-Argon 主题通过**代码块魔改**技术,在代码高亮之前拦截并转换 mermaid 代码块,因此:
-- ✅ 不会被代码高亮处理(无行号、无控制按钮)
-- ✅ 不会有字符转义问题(`-->` 保持不变)
-- ✅ 不会有嵌套结构问题
-- ✅ 完全符合标准 Markdown 语法
-- ✅ 易于迁移到其他平台
-```mermaid
-flowchart TD
-    A --> B
-```
-````
-
-**方式 2:HTML 标签**
-```html
-
-flowchart TD - A --> B -
-``` - -**注意:** 使用前需要在主题设置中启用 Mermaid 支持。 - ---- - -### Q4: 需要安装额外的插件吗? - -**A:** 不需要。Argon 主题内置了 Mermaid 支持,开箱即用。 - -**但是:** -- 如果您已经安装了 Mermaid 相关插件(如 WP Githuber MD),主题会自动检测并避免重复加载 -- 主题会智能判断是否需要加载 Mermaid 库 - ---- - -## 配置问题 - -### Q5: 如何启用 Mermaid 支持? - -**A:** 按照以下步骤操作: - -1. 登录 WordPress 后台 -2. 进入 **外观 → Argon 主题设置** -3. 找到 **Mermaid 图表** 分类 -4. 勾选 **"启用 Mermaid 支持"** -5. 点击 **"保存设置"** - ---- - -### Q6: 应该选择哪个 CDN 来源? - -**A:** 推荐选择: - -**首选:jsDelivr CDN** -- ✅ 全球 CDN,速度快 -- ✅ 稳定性高 -- ✅ 免费使用 - -**备选:unpkg CDN** -- 作为备用选项 - -**特殊情况:** -- **内网环境** → 使用本地文件 -- **特定版本需求** → 使用自定义 CDN -- **极致速度** → 下载本地文件 - -**主题优势:** 即使主 CDN 失败,主题会自动尝试备用 CDN,确保可用性。 - ---- - -### Q7: 如何使用自定义 CDN? - -**A:** 步骤如下: - -1. 在 **CDN 来源** 中选择 **"自定义 CDN 地址"** -2. 在 **自定义 CDN 地址** 输入框中填入完整的 URL -3. 保存设置 - -**URL 格式要求:** -- 必须是有效的 URL -- 必须以 `.js` 结尾 -- 必须使用 `http://` 或 `https://` 协议 - -**示例:** -``` -https://cdn.example.com/mermaid@10.0.0/mermaid.min.js -``` - ---- - -### Q8: 图表主题应该选择哪个? - -**A:** 推荐选择 **"自动切换"**。 - -**原因:** -- 自动跟随页面的日间/夜间模式 -- 日间模式使用浅色主题 -- 夜间模式使用深色主题 -- 用户体验最佳 - -**其他选项:** -- **默认主题** - 固定使用浅色 -- **深色主题** - 固定使用深色 -- **森林主题** - 绿色主题 -- **中性主题** - 灰色主题 - ---- - -### Q9: 什么时候需要启用调试模式? - -**A:** 在以下情况下启用: - -1. **图表不显示** - 查看是否有加载错误 -2. **渲染失败** - 查看详细的错误信息 -3. **主题切换异常** - 检查主题切换逻辑 -4. **CDN 加载问题** - 查看 CDN 加载状态 -5. **开发测试** - 开发新功能时调试 - -**如何使用:** -1. 启用调试模式 -2. 打开浏览器开发者工具(F12) -3. 切换到"控制台"标签 -4. 查看以 `[Argon Mermaid]` 开头的日志 - -**注意:** 生产环境建议关闭调试模式。 - ---- - -## 渲染问题 - -### Q10: 图表不显示,只显示代码怎么办? - -**A:** 按以下步骤排查: - -**步骤 1:检查是否启用** -- 确认主题设置中已启用 Mermaid 支持 - -**步骤 2:检查代码格式** -- 确认使用了正确的代码块格式 -- 检查是否有语法错误 - -**步骤 3:检查浏览器控制台** -- 按 F12 打开开发者工具 -- 查看是否有 JavaScript 错误 - -**步骤 4:启用调试模式** -- 在主题设置中启用调试模式 -- 查看详细的日志信息 - -**步骤 5:验证代码** -- 访问 [Mermaid Live Editor](https://mermaid.live/) -- 粘贴代码验证语法是否正确 - ---- - -### Q11: 图表显示"渲染失败"错误? - -**A:** 这通常是代码语法错误导致的。 - -**解决方法:** - -1. **查看错误详情** - - 点击错误提示中的"查看原始代码" - - 查看错误类型和错误信息 - -2. **常见语法错误:** - - 缺少必要的关键字 - - 箭头符号错误 - - 节点 ID 重复 - - 引号不匹配 - - 缩进不正确 - -3. **使用在线编辑器验证** - - 访问 [Mermaid Live Editor](https://mermaid.live/) - - 粘贴代码并查看错误提示 - - 根据提示修正语法 - -4. **参考官方文档** - - [Mermaid 语法参考](https://mermaid.js.org/intro/syntax-reference.html) - - 查看对应图表类型的语法规则 - ---- - -### Q12: 图表在夜间模式下看不清? - -**A:** 这是主题配置问题。 - -**解决方法:** - -1. 进入 **主题设置 → Mermaid 图表 → 外观设置** -2. 将 **图表主题** 设置为 **"自动切换"** -3. 保存设置 - -**原理:** -- 自动切换模式会检测页面主题 -- 日间模式使用浅色图表主题 -- 夜间模式使用深色图表主题 - -**如果仍有问题:** -- 清除浏览器缓存 -- 刷新页面 -- 检查是否有其他 CSS 冲突 - ---- - -### Q13: 图表太大,超出容器怎么办? - -**A:** Mermaid 图表会自动适应容器宽度,但如果图表过于复杂,可能会出现问题。 - -**解决方法:** - -**方法 1:简化图表** -- 减少节点数量 -- 拆分为多个小图表 -- 使用子图组织内容 - -**方法 2:自定义样式** -```css -.mermaid-container { - max-width: 100%; - overflow-x: auto; -} - -.mermaid-container svg { - max-width: 100%; - height: auto; -} -``` - -**方法 3:调整图表方向** -```mermaid -flowchart LR /* 横向布局 */ - A --> B -``` - -```mermaid -flowchart TD /* 纵向布局 */ - A --> B -``` - ---- - -### Q14: 如何在评论中使用 Mermaid? - -**A:** 评论中需要使用 HTML 格式。 - -**步骤:** - -1. **确保评论允许 HTML** - - 检查 WordPress 评论设置 - - 确认允许使用 HTML 标签 - -2. **使用 HTML 格式** -```html -
-flowchart LR - A --> B -
-``` - -3. **提交评论** - - 评论会在前台自动渲染为图表 - -**注意:** -- 不能使用 Markdown 代码块格式 -- 必须使用 `
` 包裹 -- 代码需要正确的换行格式 - ---- - -## 兼容性问题 - -### Q15: 与其他插件冲突怎么办? - -**A:** Argon 主题内置了智能兼容机制。 - -**自动检测的插件:** -- WP Githuber MD -- Markdown Block -- Code Syntax Block - -**兼容策略:** -1. 主题会自动检测已安装的 Mermaid 插件 -2. 如果检测到插件,主题只提供样式增强 -3. 避免重复加载 Mermaid 库 - -**查看兼容性状态:** -1. 进入 **主题设置 → Mermaid 图表 → 高级选项** -2. 查看 **插件兼容性检测** 部分 -3. 查看检测结果和建议 - -**如果仍有冲突:** -- 禁用主题的 Mermaid 支持,使用插件 -- 或禁用插件,使用主题的 Mermaid 支持 -- 不要同时启用多个 Mermaid 功能 - ---- - -### Q16: 检测到多个 Mermaid 插件怎么办? - -**A:** 这会导致重复加载和冲突。 - -**建议:** -1. **只保留一个** - 选择功能最完善的插件 -2. **或使用主题** - 禁用所有插件,使用主题的 Mermaid 支持 - -**如何选择:** -- **插件功能更多** → 禁用主题支持,使用插件 -- **主题集成更好** → 禁用插件,使用主题支持 -- **性能优先** → 使用主题支持(按需加载) - ---- - -### Q17: 在不同编辑器中如何使用? - -**A:** 不同编辑器使用方法略有不同。 - -**Gutenberg 编辑器:** -1. 添加"代码"块或"自定义 HTML"块 -2. 输入 Mermaid 代码 -3. 使用 `
` 包裹 - -**经典编辑器:** -1. 切换到"文本"模式 -2. 使用 HTML 格式 -3. 使用 `
` 包裹 - -**Markdown 编辑器(如 WP-Markdown):** -1. 使用代码块语法 -2. 指定语言为 `mermaid` -````markdown -```mermaid -flowchart TD - A --> B -``` -```` - -**WP Githuber MD:** -- 插件自带 Mermaid 支持 -- 主题会自动检测并避免冲突 -- 使用插件的 Mermaid 功能即可 - ---- - -## 性能问题 - -### Q18: Mermaid 会影响页面加载速度吗? - -**A:** 主题已做了充分的性能优化。 - -**优化措施:** - -1. **按需加载** - - 只在包含 Mermaid 代码的页面加载库 - - 没有 Mermaid 代码的页面不加载 - -2. **异步加载** - - Mermaid 库使用 async 属性异步加载 - - 不阻塞页面渲染 - -3. **CDN 加速** - - 使用全球 CDN 加速加载 - - 浏览器缓存减少重复加载 - -4. **智能检测** - - 检测插件是否已加载库 - - 避免重复加载 - -**实际影响:** -- Mermaid 库大小约 300KB(gzip 后约 100KB) -- 首次加载约 0.5-1 秒 -- 后续访问使用缓存,几乎无影响 - ---- - -### Q19: 一篇文章可以使用多少个图表? - -**A:** 技术上没有限制,但建议控制数量。 - -**建议:** -- **小型图表** - 不超过 10 个 -- **中型图表** - 不超过 5 个 -- **大型图表** - 不超过 3 个 - -**原因:** -- 过多图表会增加渲染时间 -- 影响页面性能 -- 用户体验下降 - -**优化建议:** -1. 合并相关图表 -2. 使用子图组织内容 -3. 复杂图表考虑使用图片替代 -4. 分页展示大量图表 - ---- - -### Q20: 如何优化 Mermaid 性能? - -**A:** 以下是一些优化建议: - -**1. 使用本地文件** -- 下载 Mermaid 库到主题目录 -- 启用"使用本地镜像" -- 减少网络请求 - -**2. 简化图表** -- 减少节点数量 -- 避免过于复杂的关系 -- 使用简洁的文本 - -**3. 启用浏览器缓存** -- CDN 文件会自动缓存 -- 减少重复加载 - -**4. 按需加载** -- 主题已自动实现 -- 只在需要时加载库 - -**5. 使用 CDN** -- 利用 CDN 的全球加速 -- 减少服务器负载 - ---- - -## 高级问题 - -### Q21: 如何自定义 Mermaid 样式? - -**A:** 可以通过 CSS 自定义样式。 - -**方法 1:使用主题自定义 CSS** -1. 进入 **外观 → 自定义 → 额外 CSS** -2. 添加自定义样式 - -**示例:** -```css -/* 自定义图表容器样式 */ -.mermaid-container { - background: #f5f5f5; - padding: 20px; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); -} - -/* 自定义错误提示样式 */ -.mermaid-error-container { - background: #fff3cd; - border-left: 4px solid #ffc107; -} -``` - -**方法 2:修改主题文件** -- 编辑 `style.css` -- 在 Mermaid 相关部分添加样式 -- 不推荐(主题更新会覆盖) - ---- - -### Q22: 如何导出 Mermaid 图表为图片? - -**A:** 有多种方法可以导出图表。 - -**方法 1:使用 Mermaid Live Editor** -1. 访问 [Mermaid Live Editor](https://mermaid.live/) -2. 粘贴代码 -3. 点击"Export"按钮 -4. 选择 PNG 或 SVG 格式 - -**方法 2:浏览器截图** -1. 在浏览器中打开文章 -2. 使用截图工具截取图表 -3. Windows: Win + Shift + S -4. Mac: Cmd + Shift + 4 - -**方法 3:开发者工具** -1. 右键点击图表 → 检查元素 -2. 找到 SVG 元素 -3. 右键 → Copy → Copy outerHTML -4. 保存为 .svg 文件 -5. 使用工具转换为 PNG - -**方法 4:使用插件** -- 安装浏览器截图插件 -- 如 Awesome Screenshot -- 直接截取并保存 - ---- - -### Q23: 如何在 Mermaid 代码中使用中文? - -**A:** Mermaid 完全支持中文,直接使用即可。 - -**示例:** -```mermaid -flowchart TD - 开始([开始]) --> 处理[处理数据] - 处理 --> 判断{是否成功?} - 判断 -->|是| 成功([成功]) - 判断 -->|否| 失败([失败]) -``` - -**注意事项:** -1. **引号问题** - - 如果文本包含特殊字符,使用引号包裹 - - `A["包含特殊字符的文本"]` - -2. **编码问题** - - 确保文件编码为 UTF-8 - - WordPress 默认使用 UTF-8 - -3. **字体问题** - - 确保浏览器支持中文字体 - - 现代浏览器都支持 - ---- - -### Q24: 如何在 Mermaid 中添加链接? - -**A:** Mermaid 支持为节点添加链接。 - -**语法:** -```mermaid -flowchart TD - A[节点 A] - B[节点 B] - A --> B - click A "https://example.com" "点击访问" - click B "https://example.com" _blank -``` - -**参数说明:** -- 第一个参数:节点 ID -- 第二个参数:链接 URL -- 第三个参数:提示文本或打开方式 - - `"提示文本"` - 鼠标悬停提示 - - `_blank` - 新标签页打开 - - `_self` - 当前标签页打开 - ---- - -### Q25: 如何使用 Mermaid 的高级功能? - -**A:** Mermaid 支持许多高级功能。 - -**1. 子图 (Subgraph)** -```mermaid -flowchart TD - subgraph 输入阶段 - A[接收] --> B[验证] - end - subgraph 处理阶段 - B --> C[处理] - end -``` - -**2. 样式定义** -```mermaid -flowchart TD - A[节点 A] - B[节点 B] - A --> B - style A fill:#f9f,stroke:#333,stroke-width:4px - style B fill:#bbf,stroke:#333,stroke-width:2px -``` - -**3. 类样式** -```mermaid -flowchart TD - A[节点 A]:::someclass - B[节点 B]:::someclass - classDef someclass fill:#f96,stroke:#333 -``` - -**4. 注释** -```mermaid -%% 这是注释,不会显示 -flowchart TD - A --> B %% 行尾注释 -``` - -**更多功能:** -- 查看 [Mermaid 官方文档](https://mermaid.js.org/) -- 参考各图表类型的详细语法 - ---- - -## 获取更多帮助 - -### 官方资源 - -- 📚 [Mermaid 官方文档](https://mermaid.js.org/) -- 🎨 [Mermaid Live Editor](https://mermaid.live/) -- 💬 [Mermaid GitHub](https://github.com/mermaid-js/mermaid) - -### Argon 主题支持 - -- 🐛 [提交 Issue](https://github.com/solstice23/argon-theme/issues) -- 📖 [主题文档](https://github.com/solstice23/argon-theme) -- 💡 [用户指南](mermaid-user-guide.md) - -### 社区资源 - -- 🌐 WordPress 论坛 -- 💬 主题用户群 -- 📝 技术博客和教程 - ---- - -**最后更新:** 2024-01-22 -**文档版本:** 1.0.0 diff --git a/docs/mermaid-shortcode-guide.md b/docs/mermaid-shortcode-guide.md deleted file mode 100644 index bcf8089..0000000 --- a/docs/mermaid-shortcode-guide.md +++ /dev/null @@ -1,505 +0,0 @@ -# Mermaid Shortcode 使用指南 - -## 为什么使用 Shortcode? - -在 WP-Markdown 环境下,使用 Shortcode 是最可靠的 Mermaid 图表标记方式: - -### 优点 ✅ - -1. **不依赖 WP-Markdown 的处理方式** - - 不会被 WordPress 自动格式化破坏 - - 不会将 `-->` 转换为 `–>` - - 不会丢失换行符 - -2. **在原生编辑器中清晰可见** - - 经典编辑器:文本模式下直接可见 - - Gutenberg 编辑器:使用"短代码"块 - - 易于编辑和维护 - -3. **支持参数配置** - - 可以设置主题(theme) - - 可以设置宽度(width) - - 可以设置高度(height) - - 可以设置对齐方式(align) - -4. **完全兼容** - - 与其他 Shortcode 一样使用 - - 不需要额外插件 - - 不需要修改 WP-Markdown - -### 对比其他方式 - -| 方式 | 优点 | 缺点 | -|------|------|------| -| **Shortcode** ⭐ | 可靠、易用、支持参数 | 需要记住语法 | -| 容器语法 `::: mermaid` | 符合 Markdown 规范 | 被 WP 格式化破坏 | -| 代码块 ` ```mermaid ` | 通用 | 被代码高亮干扰 | -| HTML `
` | 灵活 | 编辑不便 | - ---- - -## 基本用法 - -### 语法 - -``` -[mermaid] -flowchart TD - A[开始] --> B[处理] - B --> C[结束] -[/mermaid] -``` - -### 在经典编辑器中使用 - -1. 切换到"文本"模式(不是"可视化"模式) -2. 输入 Shortcode: - ``` - [mermaid] - 你的 Mermaid 代码 - [/mermaid] - ``` -3. 保存并预览 - -### 在 Gutenberg 编辑器中使用 - -1. 添加"短代码"块(Shortcode Block) -2. 输入 Shortcode: - ``` - [mermaid] - 你的 Mermaid 代码 - [/mermaid] - ``` -3. 保存并预览 - ---- - -## 参数说明 - -### theme - 主题 - -设置图表主题,支持以下值: -- `default` - 默认主题(浅色) -- `dark` - 深色主题 -- `forest` - 森林主题 -- `neutral` - 中性主题 - -**示例**: -``` -[mermaid theme="dark"] -flowchart TD - A --> B -[/mermaid] -``` - -### width - 宽度 - -设置图表容器宽度,支持: -- 百分比:`100%`, `80%`, `50%` -- 像素值:`800px`, `600px` -- 自动:`auto` - -**示例**: -``` -[mermaid width="80%"] -flowchart TD - A --> B -[/mermaid] -``` - -### height - 高度 - -设置图表容器高度,支持: -- 像素值:`600px`, `400px` -- 自动:`auto`(默认) - -**示例**: -``` -[mermaid height="500px"] -flowchart TD - A --> B -[/mermaid] -``` - -### align - 对齐方式 - -设置图表对齐方式,支持: -- `left` - 左对齐 -- `center` - 居中(默认) -- `right` - 右对齐 - -**示例**: -``` -[mermaid align="left"] -flowchart TD - A --> B -[/mermaid] -``` - -### 组合使用 - -``` -[mermaid theme="dark" width="80%" height="500px" align="center"] -flowchart TD - A[开始] --> B[处理] - B --> C[结束] -[/mermaid] -``` - ---- - -## 使用示例 - -### 示例 1: 简单流程图 - -``` -[mermaid] -flowchart TD - Start([开始]) --> Process[处理数据] - Process --> Decision{是否成功?} - Decision -->|是| Success[显示成功] - Decision -->|否| Error[显示错误] - Success --> End([结束]) - Error --> End -[/mermaid] -``` - -### 示例 2: 时序图 - -``` -[mermaid] -sequenceDiagram - participant User as 用户 - participant Server as 服务器 - participant DB as 数据库 - - User->>Server: 发送请求 - Server->>DB: 查询数据 - DB-->>Server: 返回数据 - Server-->>User: 返回响应 -[/mermaid] -``` - -### 示例 3: 类图 - -``` -[mermaid] -classDiagram - class Animal { - +String name - +int age - +makeSound() - } - class Dog { - +String breed - +bark() - } - class Cat { - +String color - +meow() - } - Animal <|-- Dog - Animal <|-- Cat -[/mermaid] -``` - -### 示例 4: 甘特图 - -``` -[mermaid] -gantt - title 项目进度 - dateFormat YYYY-MM-DD - section 设计 - 需求分析 :a1, 2024-01-01, 7d - UI设计 :a2, after a1, 5d - section 开发 - 前端开发 :b1, after a2, 10d - 后端开发 :b2, after a2, 12d - section 测试 - 功能测试 :c1, after b1, 5d - 性能测试 :c2, after b2, 3d -[/mermaid] -``` - -### 示例 5: 状态图 - -``` -[mermaid] -stateDiagram-v2 - [*] --> 待审核 - 待审核 --> 已通过: 审核通过 - 待审核 --> 已拒绝: 审核拒绝 - 已通过 --> [*] - 已拒绝 --> [*] -[/mermaid] -``` - -### 示例 6: 饼图 - -``` -[mermaid] -pie title 编程语言使用占比 - "JavaScript" : 386 - "Python" : 285 - "Java" : 215 - "C++" : 115 - "其他" : 85 -[/mermaid] -``` - -### 示例 7: ER 图 - -``` -[mermaid] -erDiagram - USER ||--o{ ORDER : places - ORDER ||--|{ ORDER_ITEM : contains - PRODUCT ||--o{ ORDER_ITEM : "ordered in" - - USER { - int id PK - string name - string email - } - ORDER { - int id PK - int user_id FK - date created_at - } - PRODUCT { - int id PK - string name - decimal price - } -[/mermaid] -``` - -### 示例 8: 带样式的流程图 - -``` -[mermaid] -flowchart TD - Start([用户提交评论]) --> PreProcess[预处理] - PreProcess --> CheckEnabled{启用 AI 检测?} - CheckEnabled -->|是| AICheck[AI 检测] - CheckEnabled -->|否| Save[保存评论] - AICheck --> Result{检测结果?} - Result -->|垃圾评论| Trash[移入回收站] - Result -->|正常评论| Save - - style Start fill:#e1f5e1,stroke:#2e7d32,stroke-width:2px - style Trash fill:#ff6b6b,stroke:#c62828,stroke-width:2px - style Save fill:#95e1d3,stroke:#2e7d32,stroke-width:2px -[/mermaid] -``` - ---- - -## 常见问题 - -### 1. Shortcode 不生效怎么办? - -**可能原因**: -- 主题未更新到最新版本 -- 使用了"可视化"模式编辑 - -**解决方案**: -1. 更新 Argon 主题到最新版本 -2. 切换到"文本"模式编辑 -3. 检查 Shortcode 语法是否正确 - -### 2. 图表渲染失败怎么办? - -**排查步骤**: - -1. **检查 Mermaid 语法** - - 访问 [Mermaid Live Editor](https://mermaid.live/) - - 粘贴你的代码验证语法 - -2. **查看浏览器控制台** - - 按 F12 打开开发者工具 - - 查看 Console 标签页 - - 搜索错误信息 - -3. **检查主题设置** - - WordPress 后台 → 外观 → Argon 主题选项 - - 找到"Mermaid 图表"分类 - - 确认"启用 Mermaid 支持"已开启 - -### 3. 如何迁移现有的容器语法? - -如果你之前使用了 `::: mermaid ... :::` 语法,可以批量替换: - -**查找**: -``` -::: mermaid -``` - -**替换为**: -``` -[mermaid] -``` - -**查找**: -``` -::: -``` - -**替换为**: -``` -[/mermaid] -``` - -### 4. 可以在评论中使用吗? - -不可以。Shortcode 只能在文章和页面中使用,评论中不支持。 - -### 5. 可以嵌套使用吗? - -不可以。Shortcode 不支持嵌套,每个图表需要独立的 `[mermaid]...[/mermaid]` 标签。 - ---- - -## 最佳实践 - -### 1. 使用有意义的节点 ID - -**推荐**: -``` -[mermaid] -flowchart TD - UserSubmit([用户提交]) --> Validate[验证数据] - Validate --> Save[保存数据] -[/mermaid] -``` - -**不推荐**: -``` -[mermaid] -flowchart TD - A --> B - B --> C -[/mermaid] -``` - -### 2. 添加适当的注释 - -``` -[mermaid] -flowchart TD - %% 用户流程 - Start([开始]) --> Login[登录] - - %% 验证流程 - Login --> Check{验证成功?} - Check -->|是| Dashboard[进入控制台] - Check -->|否| Error[显示错误] -[/mermaid] -``` - -### 3. 使用样式定义 - -``` -[mermaid] -flowchart TD - Success[成功] --> End - Error[错误] --> End - - style Success fill:#95e1d3,stroke:#2e7d32 - style Error fill:#ff6b6b,stroke:#c62828 -[/mermaid] -``` - -### 4. 保持图表简洁 - -- 避免过多的节点(建议 < 20 个) -- 使用子图(subgraph)组织复杂流程 -- 考虑拆分为多个图表 - -### 5. 测试后再发布 - -1. 先在 [Mermaid Live Editor](https://mermaid.live/) 中测试 -2. 确认语法正确后再粘贴到文章中 -3. 使用"预览"功能查看效果 -4. 确认无误后再发布 - ---- - -## 技术细节 - -### Shortcode 实现 - -Argon 主题在 `functions.php` 中注册了 `mermaid` shortcode: - -```php -add_shortcode('mermaid','shortcode_mermaid'); -function shortcode_mermaid($attr,$content=""){ - // 预处理内容 - $content = shortcode_content_preprocess($attr, $content); - - // 获取参数 - $theme = isset( $attr['theme'] ) ? $attr['theme'] : 'default'; - $width = isset( $attr['width'] ) ? $attr['width'] : '100%'; - $height = isset( $attr['height'] ) ? $attr['height'] : 'auto'; - $align = isset( $attr['align'] ) ? $attr['align'] : 'center'; - - // 生成 HTML - $out = '
'; - $out .= '
'; - $out .= esc_html($content); - $out .= '
'; - - return $out; -} -``` - -### JavaScript 检测 - -在 `argontheme.js` 中,Mermaid 渲染器会自动检测 `div.mermaid-shortcode` 元素: - -```javascript -const selectors = [ - 'div.mermaid-shortcode', // Shortcode 格式(推荐) - 'div.mermaid', // 标准格式 - // ... -]; -``` - -### 安全性 - -- 使用 `esc_html()` 转义输出,防止 XSS 攻击 -- 使用 `esc_attr()` 转义属性值 -- 不执行任何用户提供的 JavaScript 代码 - ---- - -## 相关资源 - -- [Mermaid 官方文档](https://mermaid.js.org/) -- [Mermaid Live Editor](https://mermaid.live/) -- [WordPress Shortcode API](https://developer.wordpress.org/plugins/shortcodes/) -- [Argon 主题文档](https://argon-docs.solstice23.top/) - ---- - -## 更新日志 - -### 2026-01-24 -- ✅ 添加 Mermaid Shortcode 支持 -- ✅ 支持 theme、width、height、align 参数 -- ✅ 自动检测和渲染 -- ✅ 完整的使用文档和示例 - ---- - -## 总结 - -使用 Shortcode 是在 WP-Markdown 环境下最可靠的 Mermaid 图表标记方式: - -1. **简单易用**:`[mermaid]...[/mermaid]` -2. **功能强大**:支持主题、尺寸、对齐等参数 -3. **完全兼容**:不需要额外插件或修改 -4. **易于维护**:在编辑器中清晰可见 - -推荐所有用户使用 Shortcode 方式编写 Mermaid 图表! diff --git a/docs/mermaid-troubleshooting.md b/docs/mermaid-troubleshooting.md deleted file mode 100644 index d4b189f..0000000 --- a/docs/mermaid-troubleshooting.md +++ /dev/null @@ -1,360 +0,0 @@ -# Mermaid 图表故障排查指南 - -## 常见错误及解决方案 - -### 1. 语法错误:`Parse error on line 1` - -#### 错误示例 -``` -Parse error on line 1: flowchart TD Sta -^ -Expecting 'NEWLINE', 'SPACE', 'GRAPH', got 'ALPHA' -``` - -#### 原因分析 -- 代码块中有多余的空格或缩进 -- WordPress 或插件添加了额外的格式 -- 代码提取不完整 - -#### 解决方案 - -**方案 1:检查代码格式** - -确保 Mermaid 代码格式正确: - -```markdown -三个反引号mermaid -flowchart TD - A[开始] --> B[结束] -三个反引号 -``` - -**注意**: -- 第一行只有 `flowchart TD`,后面不要有其他内容 -- 每行开头不要有多余的空格(除了必要的缩进) -- 使用 Tab 或 4 个空格作为缩进 - -**方案 2:使用容器语法** - -如果代码块格式有问题,尝试使用容器语法: - -```markdown -::: mermaid -flowchart TD - A[开始] --> B[结束] -::: -``` - -**方案 3:使用 Shortcode** - -最稳定的方式是使用 Shortcode: - -``` -[mermaid] -flowchart TD - A[开始] --> B[结束] -[/mermaid] -``` - -### 2. API 错误:`window.mermaid.render(...).then is not a function` - -#### 错误示例 -``` -window.mermaid.render(...).then is not a function -``` - -#### 原因分析 -- Mermaid 库版本过旧(< 10.0) -- Mermaid 库未正确加载 -- CDN 加载失败 - -#### 解决方案 - -**方案 1:检查 Mermaid 版本** - -在浏览器控制台中运行: - -```javascript -console.log(mermaid.version); -``` - -如果版本 < 10.0,需要更新 CDN 或使用兼容模式。 - -**方案 2:检查 CDN 加载** - -在浏览器控制台中运行: - -```javascript -console.log(typeof window.mermaid); -console.log(typeof window.mermaid.render); -``` - -如果输出 `undefined`,说明 Mermaid 库未加载。 - -**解决步骤**: -1. WordPress 后台 → 外观 → Argon 主题选项 -2. 找到"Mermaid 图表"设置 -3. 检查"启用 Mermaid 支持"是否开启 -4. 尝试切换不同的 CDN 源 -5. 清除浏览器缓存后刷新 - -**方案 3:使用兼容模式** - -主题已内置 Mermaid 8.x 和 10.x 的兼容代码,会自动检测并使用合适的 API。 - -如果仍然报错,在浏览器控制台查看详细日志: - -```javascript -// 启用调试模式 -localStorage.setItem('argon_mermaid_debug', 'true'); -location.reload(); -``` - -### 3. 渲染错误:图表显示不完整或错位 - -#### 原因分析 -- CSS 样式冲突 -- 容器宽度限制 -- 主题切换问题 - -#### 解决方案 - -**方案 1:检查容器宽度** - -在浏览器开发者工具中检查 `.mermaid-container` 的宽度。 - -如果宽度过小,添加自定义 CSS: - -```css -.mermaid-container { - max-width: 100%; - overflow-x: auto; -} -``` - -**方案 2:检查主题模式** - -Mermaid 图表会根据主题模式(日间/夜间)自动切换颜色。 - -如果颜色不正确: -1. 切换主题模式(日间 ↔ 夜间) -2. 刷新页面 -3. 检查主题设置中的 Mermaid 主题配置 - -**方案 3:强制重新渲染** - -在浏览器控制台中运行: - -```javascript -// 清除已渲染标记 -document.querySelectorAll('.mermaid-container').forEach(el => { - el.remove(); -}); - -// 重新渲染 -if (typeof MermaidRenderer !== 'undefined') { - MermaidRenderer.renderAllCharts(); -} -``` - -### 4. PJAX 切换后图表消失 - -#### 原因分析 -- PJAX 切换后未重新渲染 -- 代码块转换未执行 - -#### 解决方案 - -**方案 1:检查 PJAX 配置** - -确保主题的 PJAX 功能已启用: -1. WordPress 后台 → 外观 → Argon 主题选项 -2. 找到"PJAX"设置 -3. 确认已启用 - -**方案 2:手动触发渲染** - -在浏览器控制台中运行: - -```javascript -// 监听 PJAX 完成事件 -$(document).on('pjax:complete', function() { - console.log('[调试] PJAX 完成,重新渲染 Mermaid'); - if (typeof MermaidRenderer !== 'undefined') { - MermaidRenderer.renderAllCharts(); - } -}); -``` - -### 5. 代码块被代码高亮处理 - -#### 错误表现 -- Mermaid 代码块显示为普通代码 -- 有行号和复制按钮 -- 无法渲染为图表 - -#### 原因分析 -- 代码块转换未执行 -- 代码高亮在转换之前执行 - -#### 解决方案 - -**方案 1:检查执行顺序** - -在浏览器控制台中查看日志: - -``` -[Mermaid] 转换了 X 个代码块 ← 应该在代码高亮之前 -``` - -如果没有看到这条日志,说明转换函数未执行。 - -**方案 2:手动转换** - -在浏览器控制台中运行: - -```javascript -// 手动执行转换 -if (typeof convertMermaidCodeblocks === 'function') { - convertMermaidCodeblocks(); - console.log('[调试] 手动转换完成'); -} -``` - -**方案 3:使用其他标记方式** - -如果代码块转换不生效,使用容器语法或 Shortcode: - -```markdown -::: mermaid -flowchart TD - A --> B -::: -``` - -或 - -``` -[mermaid] -flowchart TD - A --> B -[/mermaid] -``` - -## 调试工具 - -### 1. 启用调试模式 - -在浏览器控制台中运行: - -```javascript -// 启用 Mermaid 调试 -localStorage.setItem('argon_mermaid_debug', 'true'); -location.reload(); -``` - -### 2. 查看转换结果 - -在浏览器控制台中运行: - -```javascript -// 查看所有转换后的容器 -document.querySelectorAll('.mermaid-from-codeblock').forEach((el, i) => { - console.log(`容器 ${i + 1}:`, el); - console.log(`代码内容:`, el.textContent); -}); -``` - -### 3. 查看 Mermaid 配置 - -在浏览器控制台中运行: - -```javascript -// 查看 Mermaid 配置 -console.log('Mermaid 版本:', mermaid.version); -console.log('Mermaid 配置:', mermaid.getConfig()); -console.log('主题配置:', window.argonMermaidConfig); -``` - -### 4. 测试代码语法 - -访问 [Mermaid Live Editor](https://mermaid.live/) 测试你的 Mermaid 代码是否正确。 - -### 5. 检查网络请求 - -在浏览器开发者工具的 Network 标签中: -1. 刷新页面 -2. 搜索 `mermaid` -3. 检查 Mermaid 库是否成功加载 -4. 查看 HTTP 状态码(应该是 200) - -## 常见问题 FAQ - -### Q: 为什么有些图表能渲染,有些不能? - -A: 可能原因: -1. 代码语法错误(使用 Mermaid Live Editor 验证) -2. 代码块格式不一致(检查缩进和空格) -3. 特殊字符被转义(使用容器语法或 Shortcode) - -### Q: 如何查看详细的错误信息? - -A: 打开浏览器控制台(F12),查看 Console 标签页,搜索 `[Mermaid]` 或 `[Argon Mermaid]`。 - -### Q: 代码块魔改功能如何工作? - -A: -1. 页面加载时,`convertMermaidCodeblocks()` 函数在代码高亮之前执行 -2. 查找所有 `
` 元素
-3. 提取纯文本代码并清理缩进
-4. 创建 `
` 容器 -5. 替换原始代码块元素 -6. Mermaid 渲染引擎检测并渲染容器 - -### Q: 如何禁用代码块魔改功能? - -A: 如果代码块魔改导致问题,可以使用容器语法或 Shortcode 代替。 - -### Q: 支持哪些 Mermaid 图表类型? - -A: 支持所有 Mermaid 官方图表类型: -- flowchart / graph(流程图) -- sequenceDiagram(时序图) -- classDiagram(类图) -- stateDiagram(状态图) -- erDiagram(实体关系图) -- gantt(甘特图) -- pie(饼图) -- journey(用户旅程图) -- gitGraph(Git 图) - -## 获取帮助 - -如果以上方法都无法解决问题: - -1. **收集信息**: - - 浏览器类型和版本 - - WordPress 版本 - - Argon 主题版本 - - 使用的插件列表 - - 完整的错误信息(控制台截图) - - Mermaid 代码示例 - -2. **检查文档**: - - [用户指南](mermaid-usage-guide.md) - - [开发者指南](mermaid-developer-guide.md) - - [FAQ](mermaid-faq.md) - -3. **联系支持**: - - GitHub Issues - - 主题论坛 - - 技术支持邮箱 - -## 更新日志 - -### 2026-01-24 -- ✅ 添加智能缩进清理功能 -- ✅ 添加 Mermaid API 版本检测 -- ✅ 支持 Mermaid 8.x 和 10.x -- ✅ 增强错误处理和调试日志 -- ✅ 修复代码提取时的空格问题 diff --git a/docs/mermaid-usage-guide.md b/docs/mermaid-usage-guide.md deleted file mode 100644 index 8376994..0000000 --- a/docs/mermaid-usage-guide.md +++ /dev/null @@ -1,439 +0,0 @@ -# Argon 主题 Mermaid 图表使用指南 - -## 推荐的标记方式 - -### 1. 标准 Markdown 代码块(推荐)⭐ - -```markdown -三个反引号mermaid -flowchart TD - A[开始] --> B[处理] - B --> C[结束] -三个反引号 -``` - -**优点**: -- ✅ 符合标准 Markdown 语法 -- ✅ 在所有 Markdown 编辑器中都能正确显示 -- ✅ 支持语法高亮(编辑器层面) -- ✅ 易于迁移到其他平台(GitHub、GitLab、Typora 等) -- ✅ 主题自动拦截处理,避免代码高亮干扰 -- ✅ 支持所有 Mermaid 图表类型 - -**工作原理**: -- 主题在代码高亮之前拦截 mermaid 代码块 -- 自动转换为 Mermaid 渲染容器 -- 完全绕过代码高亮和 WordPress 格式化 - -### 2. Markdown 容器语法(备选) - -```markdown -::: mermaid -flowchart TD - A[开始] --> B[处理] - B --> C[结束] -::: -``` - -**优点**: -- ✅ 符合 Markdown 扩展规范(VuePress、Docusaurus 等使用相同语法) -- ✅ 不会被 WP-Markdown 当作代码块处理,避免嵌套问题 -- ✅ 语法简洁,易于编写和阅读 -- ✅ 支持所有 Mermaid 图表类型 -- ✅ 在纯文本编辑器中也很清晰 -- ✅ 易于迁移到其他平台 - -### 3. Shortcode 格式(兼容) - -``` -[mermaid] -flowchart TD - A[开始] --> B[处理] - B --> C[结束] -[/mermaid] -``` - -**优点**: -- ✅ WordPress 原生支持 -- ✅ 不会被任何插件干扰 -- ✅ 兼容性最好 - -**缺点**: -- ❌ 不符合 Markdown 标准 -- ❌ 在其他平台无法使用 -- ❌ 编辑器中不显示为代码块 - -### 为什么推荐标准 Markdown 代码块? - -**标准 Markdown 代码块** (` ```mermaid `) 是最通用的方式: - -- **GitHub** 使用这种语法 -- **GitLab** 使用这种语法 -- **Typora** 使用这种语法 -- **VS Code** 使用这种语法 -- **符合 CommonMark 规范** - -Argon 主题通过**代码块魔改**技术,在代码高亮之前拦截并转换 mermaid 代码块,因此: -- 不会被代码高亮处理(无行号、无控制按钮) -- 不会有字符转义问题(`-->` 保持不变) -- 不会有嵌套结构问题 -- 完全符合标准 Markdown 语法 - ---- - -## 使用示例 - -### 流程图 - -**标准 Markdown 代码块:** -```markdown -三个反引号mermaid -flowchart LR - A[用户] --> B{登录?} - B -->|是| C[显示首页] - B -->|否| D[跳转登录页] -三个反引号 -``` - -**容器语法:** -```markdown -::: mermaid -flowchart LR - A[用户] --> B{登录?} - B -->|是| C[显示首页] - B -->|否| D[跳转登录页] -::: -``` - -### 时序图 - -```markdown -三个反引号mermaid -sequenceDiagram - Alice->>Bob: Hello Bob! - Bob-->>Alice: Hi Alice! - Alice->>Bob: How are you? -三个反引号 -``` - -### 类图 - -```markdown -三个反引号mermaid -classDiagram - class Animal { - +String name - +int age - +makeSound() - } - class Dog { - +String breed - +bark() - } - Animal <|-- Dog -三个反引号 -``` - -### 甘特图 - -```markdown -三个反引号mermaid -gantt - title 项目进度 - dateFormat YYYY-MM-DD - section 设计 - 需求分析 :a1, 2024-01-01, 7d - UI设计 :a2, after a1, 5d - section 开发 - 前端开发 :b1, after a2, 10d - 后端开发 :b2, after a2, 12d -三个反引号 -``` - -### 状态图 - -```markdown -三个反引号mermaid -stateDiagram-v2 - [*] --> 待审核 - 待审核 --> 已通过: 审核通过 - 待审核 --> 已拒绝: 审核拒绝 - 已通过 --> [*] - 已拒绝 --> [*] -三个反引号 -``` - -### 饼图 - -```markdown -三个反引号mermaid -pie title 宠物分布 - "狗" : 386 - "猫" : 85 - "兔子" : 15 -三个反引号 -``` - ---- - -## 常见问题 - -### 1. 如何在 WordPress 编辑器中使用? - -**在经典编辑器中:** -1. 切换到"文本"模式(不是"可视化"模式) -2. 直接输入容器语法 -3. 保存并预览 - -**在 Gutenberg 编辑器中:** -1. 添加"自定义 HTML"块或"代码"块 -2. 输入容器语法 -3. 保存并预览 - -### 2. 已有文章如何迁移? - -如果你的文章使用了传统的 Markdown 代码块,可以批量替换: - -**查找:** -``` -三个反引号mermaid -``` - -**替换为:** -``` -::: mermaid -``` - -**查找:** -``` -三个反引号(代码块结束) -``` - -**替换为:** -``` -::: -``` - -### 3. 容器语法不生效怎么办? - -**可能的原因:** -- 其他插件或主题处理了容器语法 -- WP-Markdown 插件版本过旧 - -**解决方案:** -1. 检查是否有其他 Markdown 插件冲突 -2. 更新 WP-Markdown 插件到最新版本 -3. 查看浏览器控制台的错误信息 - -### 4. 渲染失败怎么办? - -**排查步骤:** - -1. **检查语法** - - 访问 [Mermaid Live Editor](https://mermaid.live/) 验证语法 - - 确保图表类型正确 - -2. **查看控制台** - - 按 F12 打开开发者工具 - - 查看 Console 标签页 - - 搜索 `[Argon Mermaid]` 日志 - -3. **检查主题设置** - - WordPress 后台 → 外观 → Argon 主题选项 - - 找到"Mermaid 图表"分类 - - 确认"启用 Mermaid 支持"已开启 - -4. **测试 CDN 连接** - - 访问测试页面验证 CDN 是否可用 - - 如果 CDN 失败,尝试切换到其他 CDN - ---- - -## 最佳实践 - -### 1. 使用标准 Markdown 代码块 - -**推荐:** -```markdown -三个反引号mermaid -flowchart TD - A --> B -三个反引号 -``` - -**也可以使用容器语法:** -```markdown -::: mermaid -flowchart TD - A --> B -::: -``` - -**不推荐(Shortcode):** -``` -[mermaid] -flowchart TD - A --> B -[/mermaid] -``` - -### 2. 避免特殊字符 - -如果图表中包含特殊字符,使用引号包裹: - -```markdown -::: mermaid -flowchart TD - A["包含 <特殊> 字符"] --> B["使用引号包裹"] -::: -``` - -### 3. 测试复杂图表 - -对于复杂的图表: -1. 先在 [Mermaid Live Editor](https://mermaid.live/) 中测试 -2. 确认语法正确后再粘贴到文章中 -3. 发布前预览文章 - -### 4. 启用调试模式 - -如果遇到问题: -1. 主题设置 → Mermaid 图表 → 基本设置 -2. 开启"调试模式" -3. 刷新页面查看控制台日志 - -### 5. 保持代码简洁 - -- 使用有意义的节点 ID -- 添加适当的注释 -- 保持图表结构清晰 - ---- - -## 技术细节 - -### 支持的标记格式 - -Argon 主题支持以下格式(按优先级排序): - -1. ` ```mermaid ... ``` ` - 标准 Markdown 代码块 ⭐(推荐) -2. `::: mermaid ... :::` - Markdown 容器语法(备选) -3. `[mermaid]...[/mermaid]` - Shortcode 格式(兼容) -4. `
` - 标准格式(WPMD 生成) -5. `
` - Markdown 格式(降级)
-6. `
` - 自定义属性格式
-7. `` - 简化格式
-
-### 代码块魔改技术
-
-**工作原理**:
-
-1. **拦截阶段**(在代码高亮之前)
-   - 查找所有 `
` 元素
-   - 提取纯文本代码(使用 `textContent`)
-   - 创建 `
` 容器 - - 替换原始代码块元素 - -2. **代码高亮阶段** - - 处理其他代码块 - - 跳过 mermaid 相关的元素 - -3. **Mermaid 渲染阶段** - - 检测所有 Mermaid 容器(包括新的 `mermaid-from-codeblock`) - - 提取代码并渲染为 SVG 图表 - -**优势**: -- ✅ 完全绕过代码高亮干扰 -- ✅ 特殊字符不被转换(`-->` 保持不变) -- ✅ 换行符正确保留 -- ✅ 支持 PJAX 页面切换 -- ✅ 性能无明显影响 - -### 代码提取逻辑 - -1. **检测代码块** - - CSS 选择器查找标准格式 - - TreeWalker 查找容器语法 - - 代码块魔改:在代码高亮前拦截 - -2. **提取代码** - - 代码块魔改格式:直接提取 `textContent` - - 容器语法:移除 `::: mermaid` 和 `:::` - - WPMD 格式:正则提取 `document.write()` 内容 - - 标准格式:直接提取文本内容 - -3. **解码处理** - - 先解码 HTML 实体(`<` → `<`) - - 再解码转义字符(`\n` → 换行符) - -4. **渲染图表** - - 使用 Mermaid.js 库渲染为 SVG - - 应用主题样式(夜间模式适配) - - 错误时显示友好提示 - ---- - -## 故障排除 - -### 问题:容器语法被显示为普通文本 - -**症状**:`::: mermaid` 被显示在页面上 - -**原因**:可能被其他插件或主题处理为普通文本 - -**解决**: -- 检查是否有其他 Markdown 插件冲突 -- 确认 Argon 主题已更新到最新版本 -- 查看浏览器控制台是否有 JavaScript 错误 - -### 问题:只显示第一个单词 - -**症状**:图表渲染失败,错误信息显示 `"text": "flowchart"` - -**原因**:代码提取不完整 - -**解决**: -- 确保使用最新版本的 Argon 主题 -- 使用容器语法而不是传统代码块 -- 查看控制台日志确认提取到的完整代码 - -### 问题:HTML 实体未解码 - -**症状**:图表中显示 `<` 而不是 `<` - -**原因**:HTML 实体解码失败 - -**解决**: -- Argon 主题会自动解码 HTML 实体 -- 使用容器语法可避免此问题 -- 在 Mermaid 代码中使用引号包裹特殊字符 - ---- - -## 相关资源 - -- [Mermaid 官方文档](https://mermaid.js.org/) -- [Mermaid Live Editor](https://mermaid.live/) -- [VuePress 容器语法](https://vuepress.vuejs.org/guide/markdown.html#custom-containers) -- [CommonMark 规范](https://commonmark.org/) - ---- - -## 更新日志 - -### 2026-01-24 -- ✅ **新增代码块魔改支持**:支持标准 Markdown 代码块 (` ```mermaid `) -- ✅ 在代码高亮之前拦截并转换 mermaid 代码块 -- ✅ 完全绕过代码高亮和 WordPress 格式化 -- ✅ 特殊字符不被转换(`-->` 保持不变) -- ✅ 换行符正确保留 -- ✅ 支持 PJAX 页面切换 -- ✅ 添加 Markdown 容器语法支持(`::: mermaid ... :::`) -- ✅ 修复 WP-Markdown 格式的代码提取问题 -- ✅ 改进正则表达式,支持多行代码 -- ✅ 添加降级方案和详细日志 -- ✅ 修复代码高亮干扰 mermaid 渲染的问题(排除 mermaid 代码块) -- ✅ 修复容器语法中空行导致内容被截断的问题 -- ✅ 修复 WP-Markdown 的 document.write 重复输出问题 -- ✅ 改进容器语法检测,支持跨多个元素的内容收集 -- ✅ 修复换行符丢失问题(将 `
` 标签正确转换为换行符) diff --git a/docs/mermaid-user-guide.md b/docs/mermaid-user-guide.md deleted file mode 100644 index 3aa0aeb..0000000 --- a/docs/mermaid-user-guide.md +++ /dev/null @@ -1,719 +0,0 @@ -# Argon 主题 Mermaid 图表使用指南 - -## 目录 - -1. [功能简介](#功能简介) -2. [快速开始](#快速开始) -3. [支持的图表类型](#支持的图表类型) -4. [使用方法](#使用方法) -5. [主题设置](#主题设置) -6. [常见问题](#常见问题) -7. [最佳实践](#最佳实践) -8. [故障排除](#故障排除) - ---- - -## 功能简介 - -Argon 主题内置了 Mermaid 图表支持,让您可以在文章中轻松创建各种专业的图表,包括: - -- 📊 **流程图** - 展示业务流程和逻辑关系 -- 📈 **时序图** - 描述系统交互和时间顺序 -- 🏗️ **类图** - 展示面向对象的类结构 -- 📉 **状态图** - 表示状态机和状态转换 -- 🥧 **饼图** - 显示数据占比 -- 📅 **甘特图** - 项目进度管理 -- 🗺️ **用户旅程图** - 用户体验流程 -- 🌳 **Git 图** - 版本控制分支可视化 - -### 主要特性 - -✅ **零配置使用** - 开箱即用,无需额外插件 -✅ **自动主题切换** - 跟随页面日间/夜间模式 -✅ **智能加载** - 只在需要时加载库文件 -✅ **插件兼容** - 自动检测并避免重复加载 -✅ **错误提示** - 友好的错误信息和调试支持 -✅ **CDN 降级** - 多个 CDN 自动切换,确保可用性 - ---- - -## 快速开始 - -### 1. 启用 Mermaid 支持 - -进入 **WordPress 后台 → 外观 → Argon 主题设置 → Mermaid 图表**,勾选"启用 Mermaid 支持"。 - -### 2. 在文章中使用 - -在文章编辑器中,使用以下格式插入 Mermaid 代码: - -````markdown -```mermaid -flowchart TD - Start([开始]) --> Process[处理数据] - Process --> End([结束]) -``` -```` - -或使用 HTML 格式: - -```html -
-flowchart TD - Start([开始]) --> Process[处理数据] - Process --> End([结束]) -
-``` - -### 3. 发布并查看 - -保存文章后,在前台页面即可看到渲染后的图表。 - ---- - -## 支持的图表类型 - -### 1. 流程图 (Flowchart) - -展示流程和决策逻辑。 - -````markdown -```mermaid -flowchart TD - A[开始] --> B{判断条件} - B -->|是| C[执行操作 A] - B -->|否| D[执行操作 B] - C --> E[结束] - D --> E -``` -```` - -**节点形状:** -- `[文本]` - 矩形 -- `([文本])` - 圆角矩形 -- `{文本}` - 菱形(判断) -- `((文本))` - 圆形 -- `[[文本]]` - 子程序 - -### 2. 时序图 (Sequence Diagram) - -描述对象之间的交互顺序。 - -````markdown -```mermaid -sequenceDiagram - participant 用户 - participant 服务器 - participant 数据库 - - 用户->>服务器: 发送请求 - 服务器->>数据库: 查询数据 - 数据库-->>服务器: 返回结果 - 服务器-->>用户: 响应数据 -``` -```` - -**箭头类型:** -- `->` - 实线箭头 -- `-->` - 虚线箭头 -- `->>` - 实线箭头(带箭头) -- `-->>` - 虚线箭头(带箭头) - -### 3. 类图 (Class Diagram) - -展示面向对象的类结构。 - -````markdown -```mermaid -classDiagram - class Animal { - +String name - +int age - +makeSound() - } - class Dog { - +String breed - +bark() - } - class Cat { - +meow() - } - Animal <|-- Dog - Animal <|-- Cat -``` -```` - -### 4. 状态图 (State Diagram) - -表示状态机和状态转换。 - -````markdown -```mermaid -stateDiagram-v2 - [*] --> 待处理 - 待处理 --> 处理中: 开始处理 - 处理中 --> 已完成: 处理成功 - 处理中 --> 失败: 处理失败 - 失败 --> 待处理: 重试 - 已完成 --> [*] -``` -```` - -### 5. 饼图 (Pie Chart) - -显示数据占比。 - -````markdown -```mermaid -pie title 浏览器市场份额 - "Chrome" : 65 - "Safari" : 15 - "Firefox" : 10 - "Edge" : 7 - "其他" : 3 -``` -```` - -### 6. 甘特图 (Gantt Chart) - -项目进度管理。 - -````markdown -```mermaid -gantt - title 项目开发计划 - dateFormat YYYY-MM-DD - section 设计阶段 - 需求分析 :a1, 2024-01-01, 7d - UI 设计 :a2, after a1, 5d - section 开发阶段 - 前端开发 :b1, after a2, 10d - 后端开发 :b2, after a2, 12d - section 测试阶段 - 功能测试 :c1, after b1, 5d -``` -```` - -### 7. 用户旅程图 (User Journey) - -描述用户体验流程。 - -````markdown -```mermaid -journey - title 用户购物旅程 - section 浏览商品 - 访问网站: 5: 用户 - 搜索商品: 3: 用户 - 查看详情: 4: 用户 - section 下单 - 加入购物车: 4: 用户 - 填写信息: 2: 用户 - 支付: 3: 用户 - section 收货 - 等待发货: 2: 用户 - 收到商品: 5: 用户 -``` -```` - -### 8. Git 图 (Git Graph) - -版本控制分支可视化。 - -````markdown -```mermaid -gitGraph - commit - commit - branch develop - checkout develop - commit - commit - checkout main - merge develop - commit -``` -```` - ---- - -## 使用方法 - -### 在 Markdown 编辑器中使用 - -如果您使用 Markdown 编辑器(如 WP-Markdown),直接使用代码块语法: - -````markdown -```mermaid -flowchart LR - A --> B - B --> C -``` -```` - -### 在 Gutenberg 编辑器中使用 - -1. 添加"代码"块或"自定义 HTML"块 -2. 输入 Mermaid 代码 -3. 使用 `
` 包裹 - -```html -
-flowchart LR - A --> B - B --> C -
-``` - -### 在经典编辑器中使用 - -切换到"文本"模式,使用 HTML 格式: - -```html -
-flowchart LR - A --> B - B --> C -
-``` - ---- - -## 主题设置 - -### 基本设置 - -#### 启用 Mermaid 支持 - -**位置:** Argon 主题设置 → Mermaid 图表 → 基本设置 - -勾选此选项以启用 Mermaid 图表渲染功能。 - -#### CDN 来源 - -选择 Mermaid 库的加载来源: - -- **jsDelivr CDN** (推荐) - 全球 CDN,速度快,稳定性高 -- **unpkg CDN** - 备用 CDN -- **自定义 CDN 地址** - 使用自己的 CDN 或镜像 -- **本地文件** - 使用主题目录中的本地文件 - -**建议:** 使用 jsDelivr CDN,主题会自动在多个 CDN 之间切换以确保可用性。 - -#### 自定义 CDN 地址 - -当选择"自定义 CDN 地址"时,输入完整的 Mermaid 库 URL。 - -**格式要求:** -- 必须是有效的 URL -- 必须以 `.js` 结尾 -- 必须使用 `http://` 或 `https://` 协议 - -**示例:** -``` -https://cdn.example.com/mermaid@10.0.0/mermaid.min.js -``` - -### 外观设置 - -#### 图表主题 - -选择 Mermaid 图表的配色主题: - -- **自动切换** (推荐) - 跟随页面日间/夜间模式自动切换 -- **默认主题** - 浅色主题,适合日间模式 -- **深色主题** - 深色主题,适合夜间模式 -- **森林主题** - 绿色主题 -- **中性主题** - 灰色主题 - -**建议:** 使用"自动切换",让图表主题与页面主题保持一致。 - -### 高级选项 - -#### 使用本地镜像 - -启用后,如果检测到主题目录中存在 Mermaid 库文件,将优先使用本地文件而不是 CDN。 - -**本地文件路径:** -``` -wp-content/themes/argon/assets/vendor/mermaid/mermaid.min.js -``` - -**适用场景:** -- 内网环境无法访问外部 CDN -- 需要使用特定版本的 Mermaid -- 追求极致的加载速度 - -#### 调试模式 - -启用后,将在浏览器控制台输出详细的 Mermaid 渲染日志。 - -**日志内容包括:** -- 初始化状态 -- 检测到的代码块数量 -- 渲染成功/失败信息 -- 主题切换记录 -- CDN 加载状态 - -**使用方法:** -1. 启用调试模式 -2. 打开浏览器开发者工具(F12) -3. 切换到"控制台"标签 -4. 查看以 `[Argon Mermaid]` 开头的日志 - -#### 插件兼容性检测 - -主题会自动检测已安装的 Mermaid 插件,避免重复加载库文件。 - -**支持的插件:** -- WP Githuber MD -- Markdown Block -- Code Syntax Block - -**兼容策略:** -- 如果检测到插件,主题将只提供样式增强 -- 如果未检测到插件,主题将负责加载 Mermaid 库 -- 如果检测到多个插件,会显示警告信息 - ---- - -## 常见问题 - -### Q1: 图表不显示,只显示代码? - -**可能原因:** -1. 未启用 Mermaid 支持 -2. 代码格式不正确 -3. JavaScript 加载失败 - -**解决方法:** -1. 检查主题设置中是否启用了 Mermaid 支持 -2. 确认代码格式正确(参考本文档示例) -3. 打开浏览器控制台查看是否有错误信息 -4. 启用调试模式查看详细日志 - -### Q2: 图表显示"渲染失败"错误? - -**可能原因:** -1. Mermaid 代码语法错误 -2. 使用了不支持的图表类型 -3. 代码格式不符合规范 - -**解决方法:** -1. 检查代码语法是否正确 -2. 使用 [Mermaid Live Editor](https://mermaid.live/) 验证代码 -3. 查看错误提示中的详细信息 -4. 参考本文档中的示例代码 - -### Q3: 图表在夜间模式下看不清? - -**解决方法:** -1. 进入主题设置 → Mermaid 图表 → 外观设置 -2. 将"图表主题"设置为"自动切换" -3. 图表会自动跟随页面主题切换 - -### Q4: CDN 加载失败怎么办? - -**主题已内置降级机制:** -1. 主 CDN 失败时,自动尝试备用 CDN -2. 所有 CDN 都失败时,显示友好的错误提示 - -**手动解决:** -1. 切换到其他 CDN 来源 -2. 使用自定义 CDN 地址 -3. 下载本地文件并启用"使用本地镜像" - -### Q5: 与其他插件冲突? - -**主题已内置兼容机制:** -- 自动检测已安装的 Mermaid 插件 -- 避免重复加载库文件 -- 只提供样式增强功能 - -**如果仍有冲突:** -1. 查看插件兼容性检测结果 -2. 禁用主题的 Mermaid 支持,使用插件 -3. 或禁用插件,使用主题的 Mermaid 支持 - -### Q6: 如何在评论中使用 Mermaid? - -**方法:** -1. 评论中使用 HTML 格式 -2. 使用 `
` 包裹代码 - -**示例:** -```html -
-flowchart LR - A --> B -
-``` - -**注意:** 需要确保评论允许 HTML 标签。 - -### Q7: 图表太大,超出容器? - -**解决方法:** -1. Mermaid 图表会自动适应容器宽度 -2. 如果图表过于复杂,考虑简化或拆分 -3. 使用 CSS 自定义样式调整大小 - -**自定义样式示例:** -```css -.mermaid-container { - max-width: 100%; - overflow-x: auto; -} -``` - -### Q8: 如何导出图表为图片? - -**方法 1:使用浏览器截图** -1. 在浏览器中打开文章 -2. 使用截图工具截取图表部分 - -**方法 2:使用 Mermaid Live Editor** -1. 访问 [Mermaid Live Editor](https://mermaid.live/) -2. 粘贴代码 -3. 点击"Export"导出为 PNG/SVG - -**方法 3:使用浏览器开发者工具** -1. 右键点击图表 → 检查元素 -2. 找到 SVG 元素 -3. 复制 SVG 代码或导出为图片 - ---- - -## 最佳实践 - -### 1. 代码格式规范 - -**推荐:** -```mermaid -flowchart TD - A[开始] --> B[处理] - B --> C[结束] -``` - -**不推荐:** -```mermaid -flowchart TD -A[开始]-->B[处理] -B-->C[结束] -``` - -**建议:** -- 使用缩进保持代码可读性 -- 箭头两侧添加空格 -- 每行一个语句 - -### 2. 节点命名 - -**推荐:** -```mermaid -flowchart TD - start([开始]) - process[处理数据] - decision{是否成功?} -``` - -**不推荐:** -```mermaid -flowchart TD - a([开始]) - b[处理数据] - c{是否成功?} -``` - -**建议:** -- 使用有意义的节点 ID -- 节点文本简洁明了 -- 避免使用特殊字符 - -### 3. 图表复杂度 - -**建议:** -- 单个图表不超过 20 个节点 -- 复杂流程拆分为多个图表 -- 使用子图组织相关节点 - -**示例:** -```mermaid -flowchart TD - subgraph 输入阶段 - A[接收数据] --> B[验证数据] - end - subgraph 处理阶段 - B --> C[处理数据] - C --> D[保存结果] - end -``` - -### 4. 性能优化 - -**建议:** -- 避免在一篇文章中使用过多图表(建议不超过 10 个) -- 复杂图表考虑使用图片替代 -- 启用 CDN 加速加载 - -### 5. 可访问性 - -**建议:** -- 为图表添加文字说明 -- 使用清晰的节点文本 -- 避免仅依赖颜色传达信息 - -**示例:** -```html -
-

以下是用户注册流程图:

-
- flowchart TD - Start([用户访问注册页]) --> Input[填写信息] - Input --> Validate{验证信息} - Validate -->|通过| Register[注册成功] - Validate -->|失败| Input -
-
-``` - -### 6. 版本控制 - -**建议:** -- 在文章中记录 Mermaid 代码版本 -- 复杂图表保存源代码备份 -- 使用注释说明图表用途 - -**示例:** -```mermaid -%% 用户注册流程图 -%% 版本: 1.0 -%% 更新日期: 2024-01-20 -flowchart TD - Start --> End -``` - ---- - -## 故障排除 - -### 调试步骤 - -1. **启用调试模式** - - 进入主题设置 → Mermaid 图表 → 高级选项 - - 勾选"启用调试模式" - -2. **打开浏览器控制台** - - 按 F12 打开开发者工具 - - 切换到"控制台"标签 - -3. **查看日志信息** - - 查找以 `[Argon Mermaid]` 开头的日志 - - 记录错误信息和警告 - -4. **验证代码语法** - - 访问 [Mermaid Live Editor](https://mermaid.live/) - - 粘贴代码并检查是否有语法错误 - -5. **检查网络请求** - - 在开发者工具中切换到"网络"标签 - - 查看 Mermaid 库是否成功加载 - - 检查是否有 404 或其他错误 - -### 常见错误代码 - -#### 错误 1: Parse error on line X - -**原因:** Mermaid 代码语法错误 - -**解决:** -1. 检查代码语法是否正确 -2. 使用 Mermaid Live Editor 验证 -3. 参考官方文档修正语法 - -#### 错误 2: Mermaid 库未加载 - -**原因:** CDN 加载失败或被阻止 - -**解决:** -1. 检查网络连接 -2. 切换到其他 CDN 来源 -3. 使用本地文件 - -#### 错误 3: 主题切换失败 - -**原因:** 配置错误或 JavaScript 冲突 - -**解决:** -1. 检查主题设置是否正确 -2. 禁用其他可能冲突的插件 -3. 清除浏览器缓存 - -### 获取帮助 - -如果以上方法都无法解决问题,请: - -1. **收集信息:** - - WordPress 版本 - - Argon 主题版本 - - 浏览器类型和版本 - - 错误信息和日志 - - 问题复现步骤 - -2. **提交问题:** - - 访问 [Argon 主题 GitHub](https://github.com/solstice23/argon-theme/issues) - - 创建新 Issue - - 提供详细的问题描述和信息 - -3. **社区支持:** - - 访问主题官方论坛 - - 搜索类似问题 - - 向社区求助 - ---- - -## 相关资源 - -### 官方文档 - -- [Mermaid 官方文档](https://mermaid.js.org/) -- [Mermaid 语法参考](https://mermaid.js.org/intro/syntax-reference.html) -- [Mermaid Live Editor](https://mermaid.live/) - -### 教程和示例 - -- [Mermaid 快速入门](https://mermaid.js.org/intro/getting-started.html) -- [流程图教程](https://mermaid.js.org/syntax/flowchart.html) -- [时序图教程](https://mermaid.js.org/syntax/sequenceDiagram.html) -- [类图教程](https://mermaid.js.org/syntax/classDiagram.html) - -### 工具和插件 - -- [Mermaid Chart](https://www.mermaidchart.com/) - 在线图表编辑器 -- [VS Code Mermaid 插件](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid) -- [Chrome Mermaid 扩展](https://chrome.google.com/webstore/search/mermaid) - ---- - -## 更新日志 - -### 版本 1.0.0 (2024-01-22) - -- ✨ 初始版本发布 -- ✅ 支持所有主要图表类型 -- ✅ 自动主题切换 -- ✅ 插件兼容性检测 -- ✅ CDN 降级机制 -- ✅ 错误提示和调试支持 - ---- - -## 反馈与建议 - -如果您在使用过程中有任何问题或建议,欢迎: - -- 📧 发送邮件至主题作者 -- 💬 在 GitHub 上提交 Issue -- 🌟 为项目点赞支持 - -感谢您使用 Argon 主题! diff --git a/functions.php b/functions.php index 911d220..06172a3 100644 --- a/functions.php +++ b/functions.php @@ -4585,45 +4585,7 @@ function shortcode_video($attr,$content=""){ $out .= ""; return $out; } -add_shortcode('mermaid','shortcode_mermaid'); -function shortcode_mermaid($attr,$content=""){ - // 预处理内容:移除 WordPress 自动添加的


标签 - $content = shortcode_content_preprocess($attr, $content); - - // 获取参数 - $theme = isset( $attr['theme'] ) ? $attr['theme'] : 'default'; - $width = isset( $attr['width'] ) ? $attr['width'] : '100%'; - $height = isset( $attr['height'] ) ? $attr['height'] : 'auto'; - $align = isset( $attr['align'] ) ? $attr['align'] : 'center'; - - // 生成唯一 ID - $chart_id = 'mermaid-' . mt_rand(1000000000, 9999999999); - - // 构建输出 - $out = '

'; - $out .= '
'; - $out .= esc_html($content); - $out .= '
'; - $out .= '
'; - - return $out; -} -/** - * 从内容中移除 Mermaid shortcode,用于文章预览 - * 避免在预览中显示原始 Mermaid 代码 - * - * @param string $content 文章内容 - * @return string 移除 Mermaid shortcode 后的内容 - */ -function argon_remove_mermaid_from_preview($content) { - // 移除 [mermaid]...[/mermaid] shortcode - $content = preg_replace('/\[mermaid[^\]]*\].*?\[\/mermaid\]/is', '[Mermaid 图表]', $content); - return $content; -} add_shortcode('hide_reading_time','shortcode_hide_reading_time'); function shortcode_hide_reading_time($attr,$content=""){ return ""; @@ -10975,8 +10937,6 @@ function argon_normalize_social_url($platform, $input) { return esc_url($base_urls[$platform] . $username); } -// ========================================================================== -// Mermaid 图表支持 - 配置管理 // ========================================================================== /** @@ -11211,8 +11171,6 @@ function argon_update_mermaid_settings($settings) { ]; } -// ========================================================================== -// Mermaid 图表支持 - 库加载器 // ========================================================================== /** diff --git a/header.php b/header.php index 3c104ce..ede4a70 100644 --- a/header.php +++ b/header.php @@ -329,59 +329,47 @@ '; - - // 调用函数添加属性 - $modified_tag = argon_add_mermaid_async_attribute($original_tag, 'mermaid'); - - // 验证包含 async 属性 - if (strpos($modified_tag, 'async') === false) { - echo "❌ 失败: 缺少 async 属性\n"; - return false; - } - echo "✓ 包含 async 属性\n"; - - // 验证包含 onerror 属性 - if (strpos($modified_tag, 'onerror') === false) { - echo "❌ 失败: 缺少 onerror 属性\n"; - return false; - } - echo "✓ 包含 onerror 属性\n"; - - // 验证 onerror 调用正确的函数 - if (strpos($modified_tag, 'argonMermaidLoadFallback()') === false) { - echo "❌ 失败: onerror 函数名不正确\n"; - return false; - } - echo "✓ onerror 调用正确的函数\n"; - - echo "修改后的标签: {$modified_tag}\n"; - echo "✅ 通过: onerror 属性添加正确\n\n"; - return true; -} - -/** - * 测试 4: 验证非 Mermaid 脚本不受影响 - */ -function test_other_scripts_unaffected() { - echo "测试 4: 验证非 Mermaid 脚本不受影响\n"; - echo str_repeat('-', 50) . "\n"; - - // 模拟其他脚本标签 - $original_tag = ''; - - // 调用函数 - $modified_tag = argon_add_mermaid_async_attribute($original_tag, 'other-script'); - - // 验证标签未被修改 - if ($original_tag === $modified_tag) { - echo "✓ 非 Mermaid 脚本未被修改\n"; - echo "✅ 通过: 其他脚本不受影响\n\n"; - return true; - } else { - echo "❌ 失败: 非 Mermaid 脚本被错误修改\n"; - echo "原始: {$original_tag}\n"; - echo "修改: {$modified_tag}\n\n"; - return false; - } -} - -/** - * 测试 5: 验证 JSON 编码的备用 URL - */ -function test_json_encoded_urls() { - echo "测试 5: 验证 JSON 编码的备用 URL\n"; - echo str_repeat('-', 50) . "\n"; - - $fallback_urls = argon_get_mermaid_fallback_urls(); - $json = json_encode($fallback_urls); - - // 验证 JSON 编码成功 - if ($json === false) { - echo "❌ 失败: JSON 编码失败\n"; - return false; - } - echo "✓ JSON 编码成功\n"; - - // 验证可以解码回数组 - $decoded = json_decode($json, true); - if (!is_array($decoded)) { - echo "❌ 失败: JSON 解码失败\n"; - return false; - } - echo "✓ JSON 解码成功\n"; - - // 验证解码后的数组与原数组一致 - if ($decoded !== $fallback_urls) { - echo "❌ 失败: 解码后的数组与原数组不一致\n"; - return false; - } - echo "✓ 解码后的数组与原数组一致\n"; - - echo "JSON: {$json}\n"; - echo "✅ 通过: JSON 编码验证成功\n\n"; - return true; -} - -// 运行所有测试 -echo "\n"; -echo "=================================================\n"; -echo "Mermaid 库加载失败降级处理测试\n"; -echo "=================================================\n\n"; - -$tests = [ - 'test_fallback_urls', - 'test_fallback_script_generation', - 'test_onerror_attribute', - 'test_other_scripts_unaffected', - 'test_json_encoded_urls' -]; - -$passed = 0; -$failed = 0; - -foreach ($tests as $test) { - if ($test()) { - $passed++; - } else { - $failed++; - } -} - -// 输出测试总结 -echo "=================================================\n"; -echo "测试总结\n"; -echo "=================================================\n"; -echo "通过: {$passed} 个测试\n"; -echo "失败: {$failed} 个测试\n"; - -if ($failed === 0) { - echo "\n✅ 所有测试通过!\n"; - exit(0); -} else { - echo "\n❌ 部分测试失败,请检查实现。\n"; - exit(1); -} diff --git a/tests/test-mermaid-loader.php b/tests/test-mermaid-loader.php deleted file mode 100644 index d589528..0000000 --- a/tests/test-mermaid-loader.php +++ /dev/null @@ -1,136 +0,0 @@ -flowchart TD\nA-->B
'; -test_assert(argon_has_mermaid_content($content1), "测试 1: 检测 div class=\"mermaid\" 格式"); - -// 测试 2: 检测 code class="language-mermaid" 格式 -$content2 = '
graph LR\nA-->B
'; -test_assert(argon_has_mermaid_content($content2), "测试 2: 检测 code class=\"language-mermaid\" 格式"); - -// 测试 3: 检测 pre data-lang="mermaid" 格式 -$content3 = '
sequenceDiagram\nA->>B: Hello
'; -test_assert(argon_has_mermaid_content($content3), "测试 3: 检测 pre data-lang=\"mermaid\" 格式"); - -// 测试 4: 检测 code class="mermaid" 格式 -$content4 = 'pie title Pets\n"Dogs" : 386'; -test_assert(argon_has_mermaid_content($content4), "测试 4: 检测 code class=\"mermaid\" 格式"); - -// 测试 5: 不包含 Mermaid 代码块 -$content5 = '

This is a regular paragraph

console.log("hello")'; -test_assert(!argon_has_mermaid_content($content5), "测试 5: 不包含 Mermaid 代码块"); - -// 测试 6: 空内容 -test_assert(!argon_has_mermaid_content(''), "测试 6: 空内容返回 false"); - -// 测试 7: 检测多个 class 的情况 -$content7 = '
flowchart TD
'; -test_assert(argon_has_mermaid_content($content7), "测试 7: 检测多个 class 的情况"); - -// 测试 8: 大小写不敏感 -$content8 = '
flowchart TD
'; -test_assert(argon_has_mermaid_content($content8), "测试 8: 大小写不敏感"); - -echo "\n=== 测试 argon_get_mermaid_library_url() ===\n\n"; - -// 测试 9: jsdelivr CDN -update_option('argon_mermaid_cdn_source', 'jsdelivr'); -update_option('argon_mermaid_use_local', false); -$url9 = argon_get_mermaid_library_url(); -test_assert_contains('cdn.jsdelivr.net', $url9, "测试 9: jsdelivr CDN URL"); - -// 测试 10: unpkg CDN -update_option('argon_mermaid_cdn_source', 'unpkg'); -update_option('argon_mermaid_use_local', false); -$url10 = argon_get_mermaid_library_url(); -test_assert_contains('unpkg.com', $url10, "测试 10: unpkg CDN URL"); - -// 测试 11: 本地镜像 -update_option('argon_mermaid_use_local', true); -$url11 = argon_get_mermaid_library_url(); -test_assert_contains('/assets/vendor/mermaid/', $url11, "测试 11: 本地镜像 URL"); - -// 测试 12: 自定义 CDN(有效 URL) -update_option('argon_mermaid_cdn_source', 'custom'); -update_option('argon_mermaid_cdn_custom_url', 'https://example.com/mermaid.min.js'); -update_option('argon_mermaid_use_local', false); -$url12 = argon_get_mermaid_library_url(); -test_assert_equals('https://example.com/mermaid.min.js', $url12, "测试 12: 自定义 CDN URL"); - -// 测试 13: 自定义 CDN(无效 URL,降级到 jsdelivr) -update_option('argon_mermaid_cdn_source', 'custom'); -update_option('argon_mermaid_cdn_custom_url', 'invalid-url'); -update_option('argon_mermaid_use_local', false); -$url13 = argon_get_mermaid_library_url(); -test_assert_contains('cdn.jsdelivr.net', $url13, "测试 13: 无效自定义 URL 降级到 jsdelivr"); - -// 测试 14: 本地镜像优先级最高 -update_option('argon_mermaid_cdn_source', 'jsdelivr'); -update_option('argon_mermaid_use_local', true); -$url14 = argon_get_mermaid_library_url(); -test_assert_contains('/assets/vendor/mermaid/', $url14, "测试 14: 本地镜像优先级最高"); - -// 测试 15: 未知 CDN 来源降级到 jsdelivr -update_option('argon_mermaid_cdn_source', 'unknown-source'); -update_option('argon_mermaid_use_local', false); -$url15 = argon_get_mermaid_library_url(); -test_assert_contains('cdn.jsdelivr.net', $url15, "测试 15: 未知 CDN 来源降级到 jsdelivr"); - -echo "\n=== 测试 argon_get_mermaid_fallback_urls() ===\n\n"; - -// 测试 16: 备用 URL 列表 -$fallback_urls = argon_get_mermaid_fallback_urls(); -test_assert(is_array($fallback_urls), "测试 16: 返回数组"); -test_assert(count($fallback_urls) === 3, "测试 17: 包含 3 个备用 URL"); -test_assert_contains('cdn.jsdelivr.net', $fallback_urls[0], "测试 18: 第一个备用 URL 是 jsdelivr"); -test_assert_contains('unpkg.com', $fallback_urls[1], "测试 19: 第二个备用 URL 是 unpkg"); -test_assert_contains('/assets/vendor/mermaid/', $fallback_urls[2], "测试 20: 第三个备用 URL 是本地"); - -echo "\n=== 所有测试完成 ===\n"; diff --git a/tests/test-mermaid-wait-mechanism.md b/tests/test-mermaid-wait-mechanism.md deleted file mode 100644 index 372f050..0000000 --- a/tests/test-mermaid-wait-mechanism.md +++ /dev/null @@ -1,124 +0,0 @@ -# Mermaid 库加载等待机制测试 - -## 测试目标 -验证任务 2.1 的实现:Mermaid 库加载等待机制 - -## 实现的功能 - -### 1. waitForMermaid() 函数 -- ✅ 返回 Promise -- ✅ 检查 Mermaid 库是否已加载 -- ✅ 如果已加载,立即返回 true -- ✅ 如果未加载,使用轮询机制等待(每 100ms 检查一次) -- ✅ 设置超时机制(默认 5000ms) -- ✅ 超时后返回 false -- ✅ 记录加载时间和状态日志 - -### 2. 集成到渲染流程 -- ✅ renderAllCharts() 改为 async 函数 -- ✅ 渲染前调用 waitForMermaid() 等待库加载 -- ✅ 库加载失败时记录错误并停止渲染 -- ✅ init() 函数改为 async 函数 -- ✅ 初始化时使用 waitForMermaid() 替代原来的 setInterval - -## 测试场景 - -### 场景 1: 库已加载 -**步骤:** -1. 确保 Mermaid 库已加载 -2. 调用 waitForMermaid() -3. 验证立即返回 true - -**预期结果:** -- 函数立即返回 true -- 日志显示 "Mermaid 库已加载" - -### 场景 2: 库延迟加载 -**步骤:** -1. 页面加载时 Mermaid 库未加载 -2. 调用 waitForMermaid() -3. 500ms 后加载 Mermaid 库 -4. 验证函数等待并返回 true - -**预期结果:** -- 函数等待库加载 -- 库加载后返回 true -- 日志显示加载耗时(约 500ms) - -### 场景 3: 库加载超时 -**步骤:** -1. 页面加载时 Mermaid 库未加载 -2. 调用 waitForMermaid(2000) 设置 2 秒超时 -3. 2 秒内库未加载 -4. 验证函数超时返回 false - -**预期结果:** -- 函数等待 2 秒后返回 false -- 日志显示 "Mermaid 库加载超时(2000ms)" - -### 场景 4: PJAX 页面切换 -**步骤:** -1. 通过 PJAX 跳转到包含 Mermaid 图表的页面 -2. renderAllCharts() 被调用 -3. 验证等待库加载后再渲染 - -**预期结果:** -- 等待库加载完成 -- 成功渲染所有图表 -- 不显示原始代码 - -## 代码规范检查 - -### ✅ JSDoc 注释 -```javascript -/** - * 等待 Mermaid 库加载 - * 需求 2.6: 清除缓存后首次加载时等待 Mermaid 库完全加载 - * 需求 4.1: 开始渲染前检查 Mermaid 库是否已加载 - * 需求 4.2: Mermaid 库未加载时等待库加载或显示错误提示 - * @param {number} timeout - 超时时间(毫秒),默认 5000ms - * @returns {Promise} 是否加载成功 - */ -``` - -### ✅ 代码风格 -- Tab 缩进 -- 单引号字符串 -- 严格相等 (===) -- 语句末尾分号 -- 使用 const/let(不使用 var) - -### ✅ 错误处理 -- 超时时记录错误日志 -- 加载失败时停止渲染 -- 提供友好的错误提示 - -## 性能考虑 - -### 轮询间隔 -- 每 100ms 检查一次(平衡响应速度和性能) -- 避免过于频繁的检查 - -### 超时设置 -- 默认 5000ms(5 秒) -- 可自定义超时时间 -- 防止无限等待 - -### 日志记录 -- 记录加载耗时 -- 便于性能分析和调试 - -## 需求映射 - -| 需求 ID | 需求描述 | 实现状态 | -|---------|----------|----------| -| 2.6 | 清除缓存后首次加载时等待 Mermaid 库完全加载 | ✅ | -| 4.1 | 开始渲染前检查 Mermaid 库是否已加载 | ✅ | -| 4.2 | Mermaid 库未加载时等待库加载或显示错误提示 | ✅ | - -## 下一步 - -任务 2.1 已完成,可以继续: -- 任务 2.2: 优化 Mermaid 初始化配置 -- 任务 2.3: 添加语法错误处理和友好提示 -- 任务 2.4: 测试各种图表类型 diff --git a/tests/test-pjax-mermaid-rendering.md b/tests/test-pjax-mermaid-rendering.md deleted file mode 100644 index 9369b4c..0000000 --- a/tests/test-pjax-mermaid-rendering.md +++ /dev/null @@ -1,413 +0,0 @@ -# PJAX Mermaid 渲染测试报告 - -## 测试目标 - -验证 PJAX 页面切换后 Mermaid 代码块能被正确检测和渲染。 - -## 测试环境 - -- **测试日期**: 2024-01-22 -- **主题版本**: Argon -- **浏览器**: Chrome/Firefox/Safari -- **测试文件**: argontheme.js (行 3247-3255, 4786-5400) - -## 测试用例 - -### 测试用例 1: PJAX 页面切换后的代码块检测 - -**需求**: 1.1, 1.2, 3.1-3.5 - -**测试步骤**: -1. 在首页点击包含 Mermaid 图表的文章卡片 -2. 等待 PJAX 加载完成 -3. 检查控制台日志,确认检测到 Mermaid 代码块 -4. 检查页面 DOM,确认代码块被正确识别 - -**预期结果**: -- ✅ PJAX complete 事件触发后调用 `MermaidRenderer.renderAllCharts()` -- ✅ 控制台显示: `检测到 X 个未渲染的 Mermaid 代码块` -- ✅ 代码块元素被正确识别(`
` 或 `
`)
-- ✅ 已渲染的代码块被过滤(不会重复渲染)
-
-**实际结果**: 
-
-**状态**: ⏳ 待测试
-
----
-
-### 测试用例 2: PJAX 页面切换后的图表渲染
-
-**需求**: 1.3, 1.4, 4.1-4.6
-
-**测试步骤**:
-1. 在首页点击包含 Mermaid 图表的文章卡片
-2. 等待 PJAX 加载完成
-3. 观察页面上的 Mermaid 代码块
-4. 检查是否显示为 SVG 图表而不是原始文本
-
-**预期结果**:
-- ✅ 代码块被替换为 `.mermaid-container` 容器
-- ✅ 容器内包含渲染后的 SVG 图表
-- ✅ 图表正常显示,不是原始文本
-- ✅ 控制台显示: `准备渲染图表: mermaid-chart-xxx`
-- ✅ 控制台显示: `图表渲染成功: mermaid-chart-xxx`
-
-**实际结果**: 
-
-**状态**: ⏳ 待测试
-
----
-
-### 测试用例 3: 多次 PJAX 跳转不会重复渲染
-
-**需求**: 1.3, 3.5, 16.1-16.5
-
-**测试步骤**:
-1. 从首页跳转到文章 A(包含 Mermaid 图表)
-2. 从文章 A 跳转到文章 B(包含 Mermaid 图表)
-3. 从文章 B 返回文章 A
-4. 检查图表是否重复渲染
-
-**预期结果**:
-- ✅ 每次跳转前调用 `cleanupMermaidInstances()` 清理旧实例
-- ✅ 控制台显示: `Mermaid 实例已清理`
-- ✅ 每次跳转后重新渲染图表
-- ✅ 不会出现重复的图表或错误
-- ✅ 渲染状态标记正确更新(`data-mermaid-rendered`)
-
-**实际结果**: 
-
-**状态**: ⏳ 待测试
-
----
-
-### 测试用例 4: PJAX 加载失败的降级处理
-
-**需求**: 7.1-7.4
-
-**测试步骤**:
-1. 模拟 PJAX 加载失败(网络错误或超时)
-2. 观察页面行为
-3. 检查错误提示
-
-**预期结果**:
-- ✅ 页面回退到传统的页面跳转
-- ✅ Mermaid 图表在新页面正常渲染
-- ✅ 控制台记录错误信息
-- ✅ 用户体验不受影响
-
-**实际结果**: 
-
-**状态**: ⏳ 待测试
-
----
-
-### 测试用例 5: 不同格式的 Mermaid 代码块
-
-**需求**: 3.1-3.4
-
-**测试步骤**:
-1. 创建包含不同格式 Mermaid 代码块的测试页面:
-   - `
`
-   - `
`
-   - `
` -2. 通过 PJAX 跳转到测试页面 -3. 检查所有格式的代码块是否都被检测和渲染 - -**预期结果**: -- ✅ 所有格式的代码块都被检测到 -- ✅ 所有格式的代码块都被正确渲染 -- ✅ 控制台显示检测到的代码块数量正确 - -**实际结果**: - -**状态**: ⏳ 待测试 - ---- - -### 测试用例 6: 页面刷新后的渲染 - -**需求**: 1.5 - -**测试步骤**: -1. 通过 PJAX 跳转到包含 Mermaid 图表的文章 -2. 刷新页面(F5 或 Ctrl+R) -3. 观察图表是否正常渲染 - -**预期结果**: -- ✅ 页面刷新后图表正常渲染 -- ✅ 渲染效果与 PJAX 跳转一致 -- ✅ 没有重复渲染或错误 - -**实际结果**: - -**状态**: ⏳ 待测试 - ---- - -## 代码审查 - -### PJAX 集成代码 (argontheme.js 行 3247-3255) - -```javascript -// Mermaid 图表渲染(需求 3.6: 页面切换时重新渲染) -try { - if (typeof MermaidRenderer !== 'undefined' && MermaidRenderer.renderAllCharts) { - MermaidRenderer.renderAllCharts(); - } -} catch (err) { - ArgonDebug.error('MermaidRenderer.renderAllCharts failed:', err); -} -``` - -**审查结果**: -- ✅ 正确检查 `MermaidRenderer` 是否存在 -- ✅ 正确检查 `renderAllCharts` 方法是否存在 -- ✅ 使用 try-catch 包裹,避免错误阻塞其他功能 -- ✅ 错误日志记录完整 - ---- - -### 清理代码 (argontheme.js 行 2883-2915) - -```javascript -function cleanupMermaidInstances() { - try { - // 清理已渲染的图表记录 - if (typeof MermaidRenderer !== 'undefined' && MermaidRenderer.rendered) { - const count = MermaidRenderer.rendered.size; - MermaidRenderer.rendered.clear(); - if (count > 0) { - ArgonDebug.log(`已清理 ${count} 个 Mermaid 图表记录`); - } - } - - // 移除 Mermaid 容器的事件监听器 - document.querySelectorAll('.mermaid-container').forEach(function(container) { - // 移除工具栏事件监听器 - const toolbar = container.querySelector('.mermaid-toolbar'); - if (toolbar) { - // 克隆节点以移除所有事件监听器 - // ... - } - }); - - ArgonDebug.log('Mermaid 实例已清理'); - } catch(e) { - ArgonDebug.warn('清理 Mermaid 实例失败:', e); - } -} -``` - -**审查结果**: -- ✅ 正确清理渲染记录(`MermaidRenderer.rendered.clear()`) -- ✅ 移除事件监听器,避免内存泄漏 -- ✅ 使用 try-catch 包裹,避免清理失败影响其他功能 -- ✅ 日志记录完整 - ---- - -### 检测代码 (argontheme.js 行 4890-4930) - -```javascript -detectMermaidBlocks() { - const blocks = []; - - // 需求 3.1: 扫描所有
 元素
-	// 需求 3.2: 识别 language-mermaid 类名
-	document.querySelectorAll('pre code.language-mermaid').forEach(code => {
-		// 需求 3.5: 过滤已渲染的代码块
-		if (!this.isRendered(code)) {
-			blocks.push(code);
-		}
-	});
-	
-	// 需求 3.3: 识别 mermaid 类名
-	document.querySelectorAll('pre code.mermaid').forEach(code => {
-		// 需求 3.5: 过滤已渲染的代码块
-		if (!this.isRendered(code)) {
-			blocks.push(code);
-		}
-	});
-	
-	this.logDebug(`检测到 ${blocks.length} 个未渲染的 Mermaid 代码块`);
-	return blocks;
-}
-```
-
-**审查结果**:
-- ✅ 正确扫描 `
` 元素
-- ✅ 正确识别 `language-mermaid` 和 `mermaid` 类名
-- ✅ 正确过滤已渲染的代码块
-- ✅ 日志记录完整
-- ✅ 符合需求 3.1-3.5
-
----
-
-### 渲染状态检查 (argontheme.js 行 4932-4948)
-
-```javascript
-isRendered(element) {
-	// 检查元素是否有渲染标记
-	if (element.hasAttribute('data-mermaid-rendered')) {
-		return true;
-	}
-	
-	// 检查元素是否在 mermaid-container 容器中
-	if (element.closest('.mermaid-container') !== null) {
-		return true;
-	}
-	
-	return false;
-}
-```
-
-**审查结果**:
-- ✅ 正确检查 `data-mermaid-rendered` 属性
-- ✅ 正确检查是否在 `.mermaid-container` 容器中
-- ✅ 避免重复渲染
-- ✅ 符合需求 3.5
-
----
-
-## 测试执行计划
-
-### 阶段 1: 手动测试(推荐)
-
-1. **准备测试环境**:
-   - 在 WordPress 后台创建测试文章
-   - 在文章中添加 Mermaid 代码块
-   - 发布文章
-
-2. **执行测试**:
-   - 打开浏览器开发者工具(F12)
-   - 切换到 Console 标签
-   - 从首页点击文章卡片
-   - 观察控制台日志和页面渲染效果
-   - 记录测试结果
-
-3. **测试不同场景**:
-   - 测试多次 PJAX 跳转
-   - 测试页面刷新
-   - 测试不同格式的代码块
-   - 测试错误处理
-
-### 阶段 2: 自动化测试(可选)
-
-如果需要自动化测试,可以使用以下工具:
-- Selenium WebDriver
-- Puppeteer
-- Playwright
-
----
-
-## 已知问题
-
-### 问题 1: 代码块格式不统一
-
-**描述**: 不同的 Markdown 编辑器可能生成不同格式的代码块
-
-**影响**: 可能导致某些格式的代码块无法被检测
-
-**解决方案**: 
-- 已在 `detectMermaidBlocks()` 中支持多种格式
-- 需要测试验证所有格式都能正常工作
-
----
-
-### 问题 2: PJAX 加载时机
-
-**描述**: PJAX complete 事件触发时,DOM 可能还未完全更新
-
-**影响**: 可能导致检测不到代码块
-
-**解决方案**:
-- 已在 PJAX complete 事件中调用渲染
-- 如果仍有问题,可以添加延迟或使用 MutationObserver
-
----
-
-## 测试结论
-
-**总体评估**: ⏳ 待测试
-
-**通过的测试用例**: 0/6
-
-**失败的测试用例**: 0/6
-
-**待测试的用例**: 6/6
-
----
-
-## 下一步行动
-
-1. ✅ 代码审查完成 - 代码实现符合需求
-2. ⏳ 执行手动测试 - 验证实际效果
-3. ⏳ 记录测试结果 - 更新本文档
-4. ⏳ 修复发现的问题 - 如果有
-5. ⏳ 更新任务状态 - 标记为完成
-
----
-
-## 测试检查清单
-
-- [ ] 测试用例 1: PJAX 页面切换后的代码块检测
-- [ ] 测试用例 2: PJAX 页面切换后的图表渲染
-- [ ] 测试用例 3: 多次 PJAX 跳转不会重复渲染
-- [ ] 测试用例 4: PJAX 加载失败的降级处理
-- [ ] 测试用例 5: 不同格式的 Mermaid 代码块
-- [ ] 测试用例 6: 页面刷新后的渲染
-- [ ] 代码审查: PJAX 集成代码
-- [ ] 代码审查: 清理代码
-- [ ] 代码审查: 检测代码
-- [ ] 代码审查: 渲染状态检查
-
----
-
-## 附录: 测试数据
-
-### 测试用的 Mermaid 代码
-
-#### 流程图 (Flowchart)
-```mermaid
-flowchart TD
-    A[开始] --> B{是否登录?}
-    B -->|是| C[显示主页]
-    B -->|否| D[跳转登录页]
-    C --> E[结束]
-    D --> E
-```
-
-#### 时序图 (Sequence Diagram)
-```mermaid
-sequenceDiagram
-    participant 用户
-    participant 浏览器
-    participant 服务器
-    用户->>浏览器: 点击文章
-    浏览器->>服务器: PJAX 请求
-    服务器-->>浏览器: 返回 HTML
-    浏览器->>浏览器: 渲染 Mermaid
-```
-
-#### ER 图 (Entity Relationship Diagram)
-```mermaid
-erDiagram
-    USER ||--o{ POST : writes
-    USER {
-        int id
-        string name
-        string email
-    }
-    POST {
-        int id
-        string title
-        text content
-    }
-```
-
----
-
-**文档版本**: 1.0  
-**最后更新**: 2024-01-22  
-**测试负责人**: Kiro AI Agent