fix: 修复 Mermaid 容器语法换行符丢失问题

## 问题描述

用户提供的 AI 评论审核流程 Mermaid 图表渲染失败,错误信息显示:
- `flowchart TDStart` - `TD` 和 `Start` 之间缺少换行符
- 箭头符号被转换成全角 `–>` 而不是 `-->`

## 根本原因

WP-Markdown 插件将容器语法 `::: mermaid ... :::` 转换为 HTML 时:
1. 每一行代码都被包裹在 `<p>` 标签中
2. 行内的换行使用 `<br>` 标签表示
3. 使用 `textContent` 或 `innerText` 提取时,`<br>` 标签被忽略或转换为空格
4. 导致多行代码被合并成一行,Mermaid 语法解析失败

## 修复方案

### 1. 新增 `htmlToText()` 辅助函数

```javascript
htmlToText(html) {
	// 将 <br> 和 <br/> 转换为换行符
	let text = html.replace(/<br\s*\/?>/gi, '\n');
	// 移除其他 HTML 标签
	text = text.replace(/<[^>]+>/g, '');
	// 解码 HTML 实体
	text = text
		.replace(/&nbsp;/g, ' ')
		.replace(/&lt;/g, '<')
		.replace(/&gt;/g, '>')
		.replace(/&amp;/g, '&')
		.replace(/&quot;/g, '"')
		.replace(/&#039;/g, "'");
	return text;
}
```

### 2. 改进 `extractContainerContent()` 函数

- 使用 `innerHTML` 而不是 `textContent` 或 `innerText`
- 调用 `htmlToText()` 将 `<br>` 标签转换为换行符
- 保留所有换行符和空行
- 添加详细的调试日志

### 3. 处理流程

```
1. 检测到 ::: mermaid 开始标记
2. 提取 innerHTML(包含 <br> 标签)
3. 使用 htmlToText() 转换:
   - <br> → \n
   - 移除其他 HTML 标签
   - 解码 HTML 实体
4. 收集所有行内容
5. 使用 \n 连接所有行
6. 创建容器元素存储完整代码
```

## 测试验证

### 测试用例 1:简单流程图
```markdown
::: mermaid
flowchart TD
    A[开始] --> B[处理]
    B --> C[结束]
:::
```

### 测试用例 2:复杂流程图(AI 评论审核)
- 包含 100+ 个节点
- 包含多行文本(`<br/>` 标签)
- 包含箭头符号(`-->`, `-->`)
- 包含样式定义(`style` 语句)

### 预期结果
-  所有换行符正确保留
-  箭头符号不被转换
-  多行文本正确显示
-  样式定义正确应用

## 影响范围

- 仅影响 Markdown 容器语法(`::: mermaid ... :::`)
- 不影响其他格式(`<div class="mermaid">`, `<pre><code>` 等)
- 向后兼容,不影响现有功能

## 相关文件

- `argontheme.js` - 修改 `extractContainerContent()` 和新增 `htmlToText()`
- `tests/test-ai-comment-flow.md` - 测试文档
- `docs/mermaid-usage-guide.md` - 使用指南

## 技术细节

### HTML 结构示例

WP-Markdown 生成的 HTML:
```html
<p>::: mermaid<br>
flowchart TD<br>
    Start([用户提交评论]) --> PreProcess[预处理]<br>
:::</p>
```

### 提取过程

1. **原始 HTML**: `::: mermaid<br>flowchart TD<br>Start --> End<br>:::`
2. **htmlToText()**: `::: mermaid\nflowchart TD\nStart --> End\n:::`
3. **移除标记**: `flowchart TD\nStart --> End`
4. **最终代码**:
   ```
   flowchart TD
   Start --> End
   ```

## 注意事项

1. **性能影响**:使用 `innerHTML` 和正则替换,性能影响可忽略
2. **安全性**:只处理 Mermaid 代码块,不执行任何脚本
3. **兼容性**:支持所有现代浏览器
4. **调试**:添加详细日志,便于排查问题

## 后续优化

1. 考虑支持更多 Markdown 容器语法(如 `::: warning`, `::: tip` 等)
2. 优化正则表达式性能
3. 添加单元测试
This commit is contained in:
2026-01-24 20:49:32 +08:00
parent b6d9f8c47e
commit b4ba37a6c5
2 changed files with 173 additions and 6 deletions

View File

@@ -4520,20 +4520,31 @@ void 0;
processedElements.add(startElement); processedElements.add(startElement);
// 处理开始元素 // 处理开始元素
// 使用 innerHTML 来获取原始内容,包括 <br> 标签
let startHTML = startElement.innerHTML;
let startText = startElement.textContent.trim(); let startText = startElement.textContent.trim();
if (startText.startsWith('::: mermaid')) { if (startText.startsWith('::: mermaid')) {
foundStart = true; foundStart = true;
this.logDebug('找到容器语法开始标记,原始 HTML: ' + startHTML.substring(0, 100));
// 移除开始标记,保留同一元素中的其他内容 // 移除开始标记,保留同一元素中的其他内容
startText = startText.replace(/^:::\s*mermaid\s*/i, '').trim(); startText = startText.replace(/^:::\s*mermaid\s*/i, '').trim();
if (startText && !startText.startsWith(':::')) { if (startText && !startText.startsWith(':::')) {
codeLines.push(startText); // 如果开始标记后有内容,需要从 HTML 中提取
// 将 <br> 转换为换行符
let contentHTML = startHTML.replace(/^:::\s*mermaid\s*<br\s*\/?>/i, '');
let contentText = this.htmlToText(contentHTML);
if (contentText.trim()) {
codeLines.push(contentText);
}
} }
// 检查是否在同一元素中就有结束标记 // 检查是否在同一元素中就有结束标记
if (startText.endsWith(':::')) { if (startText.endsWith(':::')) {
foundEnd = true; foundEnd = true;
// 移除结束标记 // 移除结束标记
let lastLine = codeLines[codeLines.length - 1]; if (codeLines.length > 0) {
if (lastLine) { let lastLine = codeLines[codeLines.length - 1];
codeLines[codeLines.length - 1] = lastLine.replace(/:::\s*$/, '').trim(); codeLines[codeLines.length - 1] = lastLine.replace(/:::\s*$/, '').trim();
} }
} }
@@ -4554,15 +4565,19 @@ void 0;
if (text !== ':::') { if (text !== ':::') {
text = text.replace(/:::\s*$/, '').trim(); text = text.replace(/:::\s*$/, '').trim();
if (text) { if (text) {
codeLines.push(text); // 将 HTML 转换为文本,保留换行符
let contentText = this.htmlToText(currentElement.innerHTML);
codeLines.push(contentText);
} }
} }
break; break;
} }
// 添加当前元素的内容 // 添加当前元素的内容
if (text) { // 将 HTML 转换为文本,保留换行符
codeLines.push(text); let contentText = this.htmlToText(currentElement.innerHTML);
if (contentText.trim()) {
codeLines.push(contentText);
} else { } else {
// 空元素,添加空行 // 空元素,添加空行
codeLines.push(''); codeLines.push('');
@@ -4581,6 +4596,8 @@ void 0;
// 合并所有行 // 合并所有行
let code = codeLines.join('\n').trim(); let code = codeLines.join('\n').trim();
this.logDebug('提取的完整代码: ' + code.substring(0, 200));
if (!code) { if (!code) {
return null; return null;
} }
@@ -4597,6 +4614,27 @@ void 0;
return container; return container;
}, },
/**
* 将 HTML 转换为纯文本,保留换行符
* @param {string} html - HTML 字符串
* @returns {string} 纯文本
*/
htmlToText(html) {
// 将 <br> 和 <br/> 转换为换行符
let text = html.replace(/<br\s*\/?>/gi, '\n');
// 移除其他 HTML 标签
text = text.replace(/<[^>]+>/g, '');
// 解码 HTML 实体
text = text
.replace(/&nbsp;/g, ' ')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#039;/g, "'");
return text;
},
/** /**
* 提取代码块内容 * 提取代码块内容
* @param {HTMLElement} element - 代码块元素 * @param {HTMLElement} element - 代码块元素

View File

@@ -0,0 +1,129 @@
fix: 修复 Mermaid 容器语法换行符丢失问题
## 问题描述
用户提供的 AI 评论审核流程 Mermaid 图表渲染失败,错误信息显示:
- `flowchart TDStart` - `TD` 和 `Start` 之间缺少换行符
- 箭头符号被转换成全角 `>` 而不是 `-->`
## 根本原因
WP-Markdown 插件将容器语法 `::: mermaid ... :::` 转换为 HTML 时:
1. 每一行代码都被包裹在 `<p>` 标签中
2. 行内的换行使用 `<br>` 标签表示
3. 使用 `textContent` 或 `innerText` 提取时,`<br>` 标签被忽略或转换为空格
4. 导致多行代码被合并成一行Mermaid 语法解析失败
## 修复方案
### 1. 新增 `htmlToText()` 辅助函数
```javascript
htmlToText(html) {
// 将 <br> 和 <br/> 转换为换行符
let text = html.replace(/<br\s*\/?>/gi, '\n');
// 移除其他 HTML 标签
text = text.replace(/<[^>]+>/g, '');
// 解码 HTML 实体
text = text
.replace(/&nbsp;/g, ' ')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#039;/g, "'");
return text;
}
```
### 2. 改进 `extractContainerContent()` 函数
- 使用 `innerHTML` 而不是 `textContent` 或 `innerText`
- 调用 `htmlToText()` 将 `<br>` 标签转换为换行符
- 保留所有换行符和空行
- 添加详细的调试日志
### 3. 处理流程
```
1. 检测到 ::: mermaid 开始标记
2. 提取 innerHTML包含 <br> 标签)
3. 使用 htmlToText() 转换:
- <br> → \n
- 移除其他 HTML 标签
- 解码 HTML 实体
4. 收集所有行内容
5. 使用 \n 连接所有行
6. 创建容器元素存储完整代码
```
## 测试验证
### 测试用例 1简单流程图
```markdown
::: mermaid
flowchart TD
A[开始] --> B[处理]
B --> C[结束]
:::
```
### 测试用例 2复杂流程图AI 评论审核)
- 包含 100+ 个节点
- 包含多行文本(`<br/>` 标签)
- 包含箭头符号(`-->`, `-->`
- 包含样式定义(`style` 语句)
### 预期结果
- ✅ 所有换行符正确保留
- ✅ 箭头符号不被转换
- ✅ 多行文本正确显示
- ✅ 样式定义正确应用
## 影响范围
- 仅影响 Markdown 容器语法(`::: mermaid ... :::`
- 不影响其他格式(`<div class="mermaid">`, `<pre><code>` 等)
- 向后兼容,不影响现有功能
## 相关文件
- `argontheme.js` - 修改 `extractContainerContent()` 和新增 `htmlToText()`
- `tests/test-ai-comment-flow.md` - 测试文档
- `docs/mermaid-usage-guide.md` - 使用指南
## 技术细节
### HTML 结构示例
WP-Markdown 生成的 HTML
```html
<p>::: mermaid<br>
flowchart TD<br>
Start([用户提交评论]) --> PreProcess[预处理]<br>
:::</p>
```
### 提取过程
1. **原始 HTML**: `::: mermaid<br>flowchart TD<br>Start --> End<br>:::`
2. **htmlToText()**: `::: mermaid\nflowchart TD\nStart --> End\n:::`
3. **移除标记**: `flowchart TD\nStart --> End`
4. **最终代码**:
```
flowchart TD
Start --> End
```
## 注意事项
1. **性能影响**:使用 `innerHTML` 和正则替换,性能影响可忽略
2. **安全性**:只处理 Mermaid 代码块,不执行任何脚本
3. **兼容性**:支持所有现代浏览器
4. **调试**:添加详细日志,便于排查问题
## 后续优化
1. 考虑支持更多 Markdown 容器语法(如 `::: warning`, `::: tip` 等)
2. 优化正则表达式性能
3. 添加单元测试