# Design Document: Mermaid 代码块渲染修复 ## Overview 本设计旨在修复 Argon WordPress 主题中 Mermaid 图表渲染的关键问题。当前实现存在以下核心问题: 1. **PJAX 加载后显示原始文本**:页面切换后 Mermaid 不渲染 2. **语法解析错误**:flowchart、erDiagram 等语法解析失败 3. **功能缺失**:缺少导出、全屏查看等功能 4. **交互体验差**:工具栏遮挡内容、缩放不流畅 设计方案采用模块化架构,确保代码质量和可维护性。 ## Architecture ### 核心架构原则 1. **模块化设计**:将功能拆分为独立模块,易于维护和扩展 2. **错误隔离**:每个模块独立的错误处理,互不影响 3. **性能优先**:使用批量渲染、延迟加载等优化技术 4. **用户体验**:提供流畅的交互和友好的错误提示 ### 架构图 ``` Mermaid 渲染系统 ├── 检测模块 (MermaidDetector) │ ├── 扫描代码块 │ ├── 识别 Mermaid 语法 │ └── 过滤已渲染的块 ├── 渲染模块 (MermaidRenderer) │ ├── 初始化 Mermaid │ ├── 批量渲染 │ ├── 错误处理 │ └── 降级方案 ├── 交互模块 (MermaidInteraction) │ ├── 工具栏管理 │ ├── 缩放控制 │ ├── 拖拽控制 │ └── 全屏查看 ├── 导出模块 (MermaidExporter) │ ├── PNG 导出 │ ├── SVG 导出 │ └── 错误处理 └── 生命周期模块 (MermaidLifecycle) ├── PJAX 集成 ├── 资源清理 └── 主题同步 ``` ## Components and Interfaces ### 1. Mermaid Detector (检测模块) **职责**:检测页面中的 Mermaid 代码块 **接口**: ```javascript /** * 检测页面中的 Mermaid 代码块 * @returns {HTMLElement[]} Mermaid 代码块数组 */ function detectMermaidBlocks() { const blocks = []; // 检测

	document.querySelectorAll('pre code.language-mermaid').forEach(code => {
		if (!isRendered(code)) {
			blocks.push(code);
		}
	});
	
	// 检测 

	document.querySelectorAll('pre code.mermaid').forEach(code => {
		if (!isRendered(code)) {
			blocks.push(code);
		}
	});
	
	return blocks;
}

/**
 * 检查代码块是否已渲染
 * @param {HTMLElement} element - 代码块元素
 * @returns {boolean} 是否已渲染
 */
function isRendered(element) {
	return element.hasAttribute('data-mermaid-rendered') ||
	       element.closest('.mermaid-container') !== null;
}
```


### 2. Mermaid Renderer (渲染模块)

**职责**:渲染 Mermaid 图表

**接口**:
```javascript
/**
 * 渲染所有 Mermaid 图表
 * @returns {Promise}
 */
async function renderAllMermaidCharts() {
	const blocks = detectMermaidBlocks();
	if (blocks.length === 0) return;
	
	// 等待 Mermaid 库加载
	if (!await waitForMermaid()) {
		ArgonDebug.error('[Argon Mermaid] Mermaid 库加载失败');
		return;
	}
	
	// 初始化 Mermaid
	if (!initMermaid()) {
		ArgonDebug.error('[Argon Mermaid] Mermaid 初始化失败');
		return;
	}
	
	// 批量渲染
	for (let i = 0; i < blocks.length; i++) {
		await renderMermaidChart(blocks[i], i);
	}
}

/**
 * 等待 Mermaid 库加载
 * @param {number} timeout - 超时时间(毫秒)
 * @returns {Promise} 是否加载成功
 */
function waitForMermaid(timeout = 5000) {
	return new Promise((resolve) => {
		if (typeof window.mermaid !== 'undefined') {
			resolve(true);
			return;
		}
		
		const startTime = Date.now();
		const checkInterval = setInterval(() => {
			if (typeof window.mermaid !== 'undefined') {
				clearInterval(checkInterval);
				resolve(true);
			} else if (Date.now() - startTime > timeout) {
				clearInterval(checkInterval);
				resolve(false);
			}
		}, 100);
	});
}

/**
 * 初始化 Mermaid
 * @returns {boolean} 是否初始化成功
 */
function initMermaid() {
	if (typeof window.mermaid === 'undefined') {
		return false;
	}
	
	try {
		const theme = getMermaidTheme();
		window.mermaid.initialize({
			startOnLoad: false,
			theme: theme,
			securityLevel: 'loose',
			flowchart: {
				useMaxWidth: true,
				htmlLabels: true
			}
		});
		return true;
	} catch (e) {
		ArgonDebug.error('[Argon Mermaid] 初始化失败:', e);
		return false;
	}
}

/**
 * 渲染单个图表
 * @param {HTMLElement} element - 代码块元素
 * @param {number} index - 图表索引
 * @returns {Promise}
 */
async function renderMermaidChart(element, index) {
	const chartId = `mermaid-chart-${Date.now()}-${index}`;
	const code = element.textContent.trim();
	
	ArgonDebug.log(`[Argon Mermaid] 开始渲染: ${chartId}`);
	
	try {
		// 使用 mermaid.render API
		const result = await window.mermaid.render(`mermaid-svg-${chartId}`, code);
		
		// 创建容器
		const container = createMermaidContainer(chartId, result.svg, code);
		
		// 替换原始代码块
		element.closest('pre').replaceWith(container);
		
		// 标记已渲染
		element.setAttribute('data-mermaid-rendered', 'true');
		
		ArgonDebug.log(`[Argon Mermaid] 渲染成功: ${chartId}`);
	} catch (error) {
		ArgonDebug.error(`[Argon Mermaid] 渲染失败: ${chartId}`, error);
		
		// 显示错误
		showMermaidError(element, error, code);
	}
}
```

### 3. Mermaid Interaction (交互模块)

**职责**:管理图表交互功能

**接口**:
```javascript
/**
 * 创建 Mermaid 容器
 * @param {string} chartId - 图表 ID
 * @param {string} svg - SVG 代码
 * @param {string} code - 原始代码
 * @returns {HTMLElement} 容器元素
 */
function createMermaidContainer(chartId, svg, code) {
	const container = document.createElement('div');
	container.className = 'mermaid-container';
	container.setAttribute('data-chart-id', chartId);
	container.setAttribute('data-original-code', code);
	
	// 创建图表包装器
	const wrapper = document.createElement('div');
	wrapper.className = 'mermaid-wrapper';
	wrapper.innerHTML = svg;
	
	// 创建工具栏
	const toolbar = createMermaidToolbar(chartId);
	
	container.appendChild(toolbar);
	container.appendChild(wrapper);
	
	// 绑定交互事件
	bindMermaidInteraction(container);
	
	return container;
}

/**
 * 创建工具栏
 * @param {string} chartId - 图表 ID
 * @returns {HTMLElement} 工具栏元素
 */
function createMermaidToolbar(chartId) {
	const toolbar = document.createElement('div');
	toolbar.className = 'mermaid-toolbar';
	toolbar.innerHTML = `
		
		
		
		
		
	`;
	return toolbar;
}

/**
 * 绑定交互事件
 * @param {HTMLElement} container - 容器元素
 * @returns {void}
 */
function bindMermaidInteraction(container) {
	const wrapper = container.querySelector('.mermaid-wrapper');
	const svg = wrapper.querySelector('svg');
	
	let scale = 1;
	let translateX = 0;
	let translateY = 0;
	let isDragging = false;
	let startX = 0;
	let startY = 0;
	
	// 缩放功能
	container.querySelector('.mermaid-zoom-in').addEventListener('click', () => {
		scale = Math.min(scale * 1.2, 5);
		updateTransform();
	});
	
	container.querySelector('.mermaid-zoom-out').addEventListener('click', () => {
		scale = Math.max(scale / 1.2, 0.5);
		updateTransform();
	});
	
	container.querySelector('.mermaid-reset').addEventListener('click', () => {
		scale = 1;
		translateX = 0;
		translateY = 0;
		updateTransform();
	});
	
	// 鼠标滚轮缩放
	wrapper.addEventListener('wheel', (e) => {
		e.preventDefault();
		const delta = e.deltaY > 0 ? 0.9 : 1.1;
		scale = Math.max(0.5, Math.min(5, scale * delta));
		updateTransform();
	}, { passive: false });
	
	// 拖拽功能
	wrapper.addEventListener('mousedown', (e) => {
		if (scale > 1) {
			isDragging = true;
			startX = e.clientX - translateX;
			startY = e.clientY - translateY;
			wrapper.style.cursor = 'grabbing';
		}
	});
	
	document.addEventListener('mousemove', (e) => {
		if (isDragging) {
			translateX = e.clientX - startX;
			translateY = e.clientY - startY;
			updateTransform();
		}
	});
	
	document.addEventListener('mouseup', () => {
		if (isDragging) {
			isDragging = false;
			wrapper.style.cursor = scale > 1 ? 'grab' : 'default';
		}
	});
	
	// 更新变换
	function updateTransform() {
		svg.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
		svg.style.transformOrigin = 'center';
		svg.style.transition = 'transform 0.2s ease';
		wrapper.style.cursor = scale > 1 ? 'grab' : 'default';
	}
	
	// 全屏查看
	container.querySelector('.mermaid-fullscreen').addEventListener('click', () => {
		openMermaidFullscreen(container);
	});
	
	// 导出功能
	container.querySelector('.mermaid-export').addEventListener('click', () => {
		showExportMenu(container);
	});
}
```


### 4. Mermaid Exporter (导出模块)

**职责**:导出图表为图片文件

**接口**:
```javascript
/**
 * 显示导出菜单
 * @param {HTMLElement} container - 容器元素
 * @returns {void}
 */
function showExportMenu(container) {
	const menu = document.createElement('div');
	menu.className = 'mermaid-export-menu';
	menu.innerHTML = `
		
		
	`;
	
	menu.querySelector('.export-png').addEventListener('click', () => {
		exportMermaidAsPNG(container);
		menu.remove();
	});
	
	menu.querySelector('.export-svg').addEventListener('click', () => {
		exportMermaidAsSVG(container);
		menu.remove();
	});
	
	container.appendChild(menu);
	
	// 点击外部关闭菜单
	setTimeout(() => {
		document.addEventListener('click', function closeMenu(e) {
			if (!menu.contains(e.target)) {
				menu.remove();
				document.removeEventListener('click', closeMenu);
			}
		});
	}, 0);
}

/**
 * 导出为 PNG
 * @param {HTMLElement} container - 容器元素
 * @returns {void}
 */
function exportMermaidAsPNG(container) {
	const svg = container.querySelector('svg');
	const chartId = container.getAttribute('data-chart-id');
	
	try {
		// 创建 canvas
		const canvas = document.createElement('canvas');
		const ctx = canvas.getContext('2d');
		
		// 获取 SVG 尺寸
		const bbox = svg.getBBox();
		canvas.width = bbox.width;
		canvas.height = bbox.height;
		
		// 将 SVG 转换为图片
		const svgData = new XMLSerializer().serializeToString(svg);
		const img = new Image();
		const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
		const url = URL.createObjectURL(blob);
		
		img.onload = () => {
			ctx.drawImage(img, 0, 0);
			URL.revokeObjectURL(url);
			
			// 下载
			canvas.toBlob((blob) => {
				const link = document.createElement('a');
				link.download = `${chartId}.png`;
				link.href = URL.createObjectURL(blob);
				link.click();
				URL.revokeObjectURL(link.href);
			});
		};
		
		img.src = url;
	} catch (error) {
		ArgonDebug.error('[Argon Mermaid] PNG 导出失败:', error);
		alert('导出失败,请重试');
	}
}

/**
 * 导出为 SVG
 * @param {HTMLElement} container - 容器元素
 * @returns {void}
 */
function exportMermaidAsSVG(container) {
	const svg = container.querySelector('svg');
	const chartId = container.getAttribute('data-chart-id');
	
	try {
		const svgData = new XMLSerializer().serializeToString(svg);
		const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
		const url = URL.createObjectURL(blob);
		
		const link = document.createElement('a');
		link.download = `${chartId}.svg`;
		link.href = url;
		link.click();
		
		URL.revokeObjectURL(url);
	} catch (error) {
		ArgonDebug.error('[Argon Mermaid] SVG 导出失败:', error);
		alert('导出失败,请重试');
	}
}
```

### 5. Mermaid Lifecycle (生命周期模块)

**职责**:管理 Mermaid 的生命周期

**接口**:
```javascript
/**
 * 清理 Mermaid 实例
 * @returns {void}
 */
function cleanupMermaidInstances() {
	// 移除所有容器
	document.querySelectorAll('.mermaid-container').forEach(container => {
		// 移除事件监听器
		const toolbar = container.querySelector('.mermaid-toolbar');
		if (toolbar) {
			toolbar.querySelectorAll('button').forEach(btn => {
				btn.replaceWith(btn.cloneNode(true));
			});
		}
		
		// 移除容器
		container.remove();
	});
	
	ArgonDebug.log('[Argon Mermaid] 实例已清理');
}

/**
 * PJAX 集成
 * @returns {void}
 */
function initMermaidPjaxIntegration() {
	// PJAX 开始前清理
	$(document).on('pjax:beforeReplace', function() {
		cleanupMermaidInstances();
	});
	
	// PJAX 完成后渲染
	$(document).on('pjax:complete', function() {
		renderAllMermaidCharts();
	});
}

/**
 * 主题同步
 * @returns {void}
 */
function syncMermaidTheme() {
	const isDark = document.documentElement.classList.contains('darkmode');
	const theme = isDark ? 'dark' : 'default';
	
	if (typeof window.mermaid !== 'undefined') {
		window.mermaid.initialize({
			theme: theme
		});
		
		// 重新渲染所有图表
		renderAllMermaidCharts();
	}
}

/**
 * 获取 Mermaid 主题
 * @returns {string} 主题名称
 */
function getMermaidTheme() {
	const isDark = document.documentElement.classList.contains('darkmode');
	return isDark ? 'dark' : 'default';
}
```

### 6. Error Handler (错误处理模块)

**职责**:处理渲染错误

**接口**:
```javascript
/**
 * 显示 Mermaid 错误
 * @param {HTMLElement} element - 代码块元素
 * @param {Error} error - 错误对象
 * @param {string} code - 原始代码
 * @returns {void}
 */
function showMermaidError(element, error, code) {
	const errorContainer = document.createElement('div');
	errorContainer.className = 'mermaid-error';
	errorContainer.innerHTML = `
		
Mermaid 图表渲染失败
${error.message || '未知错误'}
查看原始代码
${escapeHtml(code)}
`; element.closest('pre').replaceWith(errorContainer); } /** * 转义 HTML * @param {string} html - HTML 字符串 * @returns {string} 转义后的字符串 */ function escapeHtml(html) { const div = document.createElement('div'); div.textContent = html; return div.innerHTML; } ``` ## Data Models ### MermaidChart (图表数据模型) ```javascript /** * Mermaid 图表数据模型 */ class MermaidChart { constructor(id, code, element) { this.id = id; // 图表 ID this.code = code; // 原始代码 this.element = element; // DOM 元素 this.rendered = false; // 是否已渲染 this.scale = 1; // 缩放级别 this.translateX = 0; // X 轴偏移 this.translateY = 0; // Y 轴偏移 this.error = null; // 错误信息 } /** * 标记为已渲染 */ markRendered() { this.rendered = true; this.element.setAttribute('data-mermaid-rendered', 'true'); } /** * 设置错误 */ setError(error) { this.error = error; } /** * 重置变换 */ resetTransform() { this.scale = 1; this.translateX = 0; this.translateY = 0; } } ``` ### MermaidConfig (配置模型) ```javascript /** * Mermaid 配置模型 */ class MermaidConfig { constructor() { this.theme = 'default'; // 主题 this.startOnLoad = false; // 自动加载 this.securityLevel = 'loose'; // 安全级别 this.flowchart = { useMaxWidth: true, htmlLabels: true }; } /** * 从全局配置加载 */ loadFromGlobal() { this.theme = getMermaidTheme(); } /** * 转换为 Mermaid 配置对象 */ toMermaidConfig() { return { theme: this.theme, startOnLoad: this.startOnLoad, securityLevel: this.securityLevel, flowchart: this.flowchart }; } } ``` ## Error Handling ### 错误处理策略 1. **渲染错误**:显示友好的错误提示,保留原始代码 2. **库加载失败**:显示加载失败提示,提供重试选项 3. **导出失败**:显示导出失败提示,记录错误日志 4. **语法错误**:显示语法错误位置和错误信息 ### 错误处理实现 ```javascript /** * 安全执行函数 * @param {Function} fn - 要执行的函数 * @param {string} moduleName - 模块名称 * @returns {boolean} 是否执行成功 */ function safeExecute(fn, moduleName) { try { fn(); ArgonDebug.log(`[Argon Mermaid] ${moduleName} 执行成功`); return true; } catch (error) { ArgonDebug.error(`[Argon Mermaid] ${moduleName} 执行失败:`, error); return false; } } ``` ## Performance Optimization ### 优化策略 1. **批量渲染**:使用 requestAnimationFrame 批量渲染图表 2. **延迟加载**:优先渲染可见图表,延迟渲染视口外的图表 3. **缓存优化**:缓存已渲染的图表,避免重复渲染 4. **硬件加速**:使用 CSS transform 实现缩放和拖拽 ### 性能优化实现 ```javascript /** * 批量渲染图表 * @param {HTMLElement[]} blocks - 代码块数组 * @returns {Promise} */ async function batchRenderCharts(blocks) { for (let i = 0; i < blocks.length; i++) { await new Promise(resolve => { requestAnimationFrame(async () => { await renderMermaidChart(blocks[i], i); resolve(); }); }); } } ``` ## Testing Strategy ### 测试方法 1. **单元测试**:测试各个模块的功能 2. **集成测试**:测试模块之间的集成 3. **端到端测试**:测试完整的渲染流程 4. **性能测试**:测试渲染性能和内存占用 ### 测试用例 ```javascript describe('Mermaid Renderer', () => { it('should detect mermaid blocks', () => { const blocks = detectMermaidBlocks(); expect(blocks.length).toBeGreaterThan(0); }); it('should render mermaid chart', async () => { const block = document.querySelector('code.language-mermaid'); await renderMermaidChart(block, 0); expect(block.hasAttribute('data-mermaid-rendered')).toBe(true); }); it('should handle render error', async () => { const block = createMockBlock('invalid syntax'); await renderMermaidChart(block, 0); expect(document.querySelector('.mermaid-error')).not.toBeNull(); }); }); ``` ## Code Quality Standards ### 代码规范 1. **命名规范**:使用驼峰命名法,函数名动词开头 2. **注释规范**:使用 JSDoc 注释,说明参数和返回值 3. **错误处理**:所有异步操作都要有错误处理 4. **性能优化**:避免频繁的 DOM 操作,使用批量处理 ### 代码审查清单 - [ ] 所有函数都有 JSDoc 注释 - [ ] 所有异步操作都有错误处理 - [ ] 所有事件监听器都在清理时移除 - [ ] 使用 let/const 而不是 var - [ ] 遵循项目代码规范(Tab 缩进、单引号等) - [ ] 没有 console.log,使用 ArgonDebug - [ ] 性能优化(批量 DOM 操作、requestAnimationFrame)