fix: 修复 Mermaid 渲染问题

- 启用代码块转换功能(移除 convertMermaidCodeblocks 中的 return 语句)
- 添加完整的 Mermaid 代码块检测选择器
- 修复首页预览中显示原始 Mermaid 代码的问题
- 添加 argon_remove_mermaid_from_preview 函数过滤预览内容
- 更新三个文章预览模板,在预览中用 [Mermaid 图表] 替代原始代码
This commit is contained in:
2026-01-27 00:28:25 +08:00
parent b2f40fcf46
commit 135c2269c7
5 changed files with 635 additions and 442 deletions

View File

@@ -1,4 +1,4 @@
/*!
/*!
* Argon 主题核心 JavaScript
*/
@@ -3034,6 +3034,23 @@ function resetGT4Captcha() {
}
}
function handleHashNavigation() {
if (!location.hash) {
return;
}
let target = null;
try {
target = document.getElementById(decodeURIComponent(location.hash.slice(1)));
} catch (e) {
target = document.querySelector(location.hash);
}
if (!target) {
return;
}
try { highlightJsRender(); } catch (err) { ArgonDebug.error('highlightJsRender failed:', err); }
try { lazyloadInit(); } catch (err) { ArgonDebug.error('lazyloadInit failed:', err); }
}
// ==========================================================================
// 内联脚本执行器 (Inline Script Executor)
// ==========================================================================
@@ -3246,12 +3263,16 @@ $(document).pjax("a[href]:not([no-pjax]):not(.no-pjax):not([target='_blank']):no
// 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");
@@ -3287,6 +3308,17 @@ $(document).pjax("a[href]:not([no-pjax]):not(.no-pjax):not([target='_blank']):no
resetGT4Captcha();
});
window.addEventListener('hashchange', function() {
handleHashNavigation();
});
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
handleHashNavigation();
});
} else {
handleHashNavigation();
}
/*Reference 跳转*/
$(document).on("click", ".reference-link , .reference-list-backlink" , function(e){
e.preventDefault();
@@ -4365,7 +4397,7 @@ function convertMermaidCodeblocks(){
// 创建容器
const container = document.createElement('div');
container.className = 'mermaid-from-codeblock';
container.className = 'mermaid mermaid-from-codeblock';
container.textContent = code;
container.dataset.processed = 'true';
@@ -4797,16 +4829,17 @@ void 0;
initialized: false,
rendered: new Set(), // 已渲染的图表 ID 集合
config: null,
compatibilityFallbackAttempted: false,
/**
* 等待 Mermaid 库加载
* 需求 2.6: 清除缓存后首次加载时等待 Mermaid 库完全加载
* 需求 4.1: 开始渲染前检查 Mermaid 库是否已加载
* 需求 4.2: Mermaid 库未加载时等待库加载或显示错误提示
* @param {number} timeout - 超时时间(毫秒),默认 5000ms
* @param {number} timeout - 超时时间(毫秒),默认 10000ms (增加超时时间以应对慢速网络)
* @returns {Promise<boolean>} 是否加载成功
*/
waitForMermaid(timeout = 5000) {
waitForMermaid(timeout = 10000) {
return new Promise((resolve) => {
// 如果已经加载,直接返回成功
if (typeof window.mermaid !== 'undefined') {
@@ -4830,7 +4863,22 @@ void 0;
else if (Date.now() - startTime > timeout) {
clearInterval(checkInterval);
this.logError(`Mermaid 库加载超时(${timeout}ms`);
resolve(false);
// 尝试手动触发降级加载
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 检查一次
});
@@ -4871,56 +4919,61 @@ void 0;
// 配置 Mermaid
// 需求 2.1-2.4: 支持多种图表类型,设置正确的主题和安全级别
try {
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;
// 检查 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;
@@ -4951,22 +5004,27 @@ void 0;
*/
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' // 简化格式
];
// 需求 3.1: 扫描所有 <pre><code> 元素
// 需求 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);
}
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 代码块`);
@@ -4985,6 +5043,22 @@ void 0;
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;
@@ -4992,233 +5066,6 @@ void 0;
return false;
},
/**
* 检查元素是否在 HTML 注释中
* @param {HTMLElement} element - 要检查的元素
* @returns {boolean} 是否在注释中
*/
isInComment(element) {
let node = element.parentNode;
while (node) {
if (node.nodeType === Node.COMMENT_NODE) {
return true;
}
node = node.parentNode;
}
return false;
},
/**
* 检测 Markdown 容器语法的 Mermaid 代码块
* 格式: ::: mermaid ... :::
* @param {Array} blocks - 代码块数组
*/
detectContainerBlocks(blocks) {
// 查找所有包含 ::: mermaid 的元素
const allElements = document.querySelectorAll('p, pre, code, div');
const processedElements = new Set();
allElements.forEach(element => {
// 跳过已处理的元素
if (processedElements.has(element)) {
return;
}
const text = element.textContent.trim();
// 检查是否是开始标记
if (text.startsWith('::: mermaid') || text === '::: mermaid') {
this.logDebug('找到容器语法开始标记');
// 收集所有内容直到结束标记
const container = this.extractContainerContent(element, processedElements);
if (container && !blocks.includes(container)) {
blocks.push(container);
this.logDebug('检测到 Markdown 容器语法的 Mermaid 代码块');
}
}
});
},
/**
* 提取 Markdown 容器语法的内容
* @param {HTMLElement} startElement - 包含开始标记的元素
* @param {Set} processedElements - 已处理的元素集合
* @returns {HTMLElement|null} 包含代码的容器元素
*/
extractContainerContent(startElement, processedElements) {
let codeLines = [];
let currentElement = startElement;
let foundStart = false;
let foundEnd = false;
// 标记开始元素为已处理
processedElements.add(startElement);
// 处理开始元素
// 使用 innerHTML 来获取原始内容,包括 <br> 标签
let startHTML = startElement.innerHTML;
let startText = startElement.textContent.trim();
this.logDebug('检查元素textContent: ' + startText.substring(0, 50));
this.logDebug('innerHTML: ' + startHTML.substring(0, 100));
if (startText.startsWith('::: mermaid')) {
foundStart = true;
this.logDebug('找到容器语法开始标记');
// 检查是否整个内容都在一个元素中
if (startText.includes(':::') && startText.lastIndexOf(':::') > 10) {
// 整个容器语法在一个元素中
this.logDebug('检测到单元素容器语法');
// 使用 htmlToText 转换整个 HTML
let fullText = this.htmlToText(startHTML);
this.logDebug('转换后的完整文本: ' + fullText.substring(0, 200));
// 移除开始和结束标记
fullText = fullText.replace(/^:::\s*mermaid\s*/i, '').trim();
fullText = fullText.replace(/:::\s*$/, '').trim();
this.logDebug('移除标记后的代码: ' + fullText.substring(0, 200));
if (fullText) {
// 创建容器
const container = document.createElement('div');
container.className = 'mermaid-container-block';
container.textContent = fullText;
container.dataset.containerBlock = 'true';
// 替换开始元素
startElement.parentNode.replaceChild(container, startElement);
return container;
}
return null;
}
// 多元素容器语法(原有逻辑)
// 移除开始标记,保留同一元素中的其他内容
startText = startText.replace(/^:::\s*mermaid\s*/i, '').trim();
if (startText && !startText.startsWith(':::')) {
// 如果开始标记后有内容,需要从 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(':::')) {
foundEnd = true;
// 移除结束标记
if (codeLines.length > 0) {
let lastLine = codeLines[codeLines.length - 1];
codeLines[codeLines.length - 1] = lastLine.replace(/:::\s*$/, '').trim();
}
}
}
// 如果还没找到结束标记,继续查找后续兄弟元素
if (foundStart && !foundEnd) {
currentElement = startElement.nextElementSibling;
while (currentElement) {
processedElements.add(currentElement);
let text = currentElement.textContent.trim();
// 检查是否是结束标记
if (text === ':::' || text.endsWith(':::')) {
foundEnd = true;
// 如果结束标记前还有内容,保留它
if (text !== ':::') {
text = text.replace(/:::\s*$/, '').trim();
if (text) {
// 将 HTML 转换为文本,保留换行符
let contentText = this.htmlToText(currentElement.innerHTML);
codeLines.push(contentText);
}
}
break;
}
// 添加当前元素的内容
// 将 HTML 转换为文本,保留换行符
let contentText = this.htmlToText(currentElement.innerHTML);
if (contentText.trim()) {
codeLines.push(contentText);
} else {
// 空元素,添加空行
codeLines.push('');
}
currentElement = currentElement.nextElementSibling;
}
}
// 如果没有找到完整的容器语法,返回 null
if (!foundStart || !foundEnd) {
this.logDebug('容器语法不完整,跳过');
return null;
}
// 合并所有行
let code = codeLines.join('\n').trim();
this.logDebug('提取的完整代码: ' + code.substring(0, 200));
if (!code) {
return null;
}
// 创建一个新的容器来存储代码
const container = document.createElement('div');
container.className = 'mermaid-container-block';
container.textContent = code;
container.dataset.containerBlock = 'true';
// 替换开始元素
startElement.parentNode.replaceChild(container, startElement);
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, "'")
.replace(/&#8211;/g, '-') // EN DASH
.replace(/&#8212;/g, '--') // EM DASH
.replace(/&#8594;/g, '->') // RIGHTWARDS ARROW
.replace(/&ndash;/g, '-') // EN DASH (named entity)
.replace(/&mdash;/g, '--') // EM DASH (named entity)
.replace(/&rarr;/g, '->'); // RIGHTWARDS ARROW (named entity)
// 转换 Unicode 字符WordPress 可能直接输出 Unicode
text = text
.replace(//g, '-') // U+2013 EN DASH
.replace(/—/g, '--') // U+2014 EM DASH
.replace(/→/g, '->'); // U+2192 RIGHTWARDS ARROW
return text;
},
/**
* 提取代码块内容
* @param {HTMLElement} element - 代码块元素
@@ -5274,7 +5121,7 @@ void 0;
const clonedElement = element.cloneNode(true);
const scripts = clonedElement.querySelectorAll('script');
scripts.forEach(script => script.remove());
code = clonedElement.textContent;
code = clonedElement.innerText || clonedElement.textContent;
this.logDebug('使用降级方案提取代码');
}
}
@@ -5283,22 +5130,32 @@ void 0;
// WP-Markdown 会在 script 后面再输出一次代码
scriptTag.remove();
} else {
code = element.textContent;
this.logDebug('从纯文本提取代码');
// 检查是否包含块级元素或换行标签
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') {
// 修复:使用 innerText 而不是 textContent保留换行符
// textContent 可能会丢失某些格式化信息
code = element.innerText || element.textContent;
this.logDebug('从 CODE 标签提取,使用 innerText');
code = element.textContent || '';
this.logDebug('从 CODE 标签提取');
} else if (element.tagName === 'PRE') {
const codeElement = element.querySelector('code');
if (codeElement) {
code = codeElement.innerText || codeElement.textContent;
code = codeElement.textContent || '';
} else {
code = element.innerText || element.textContent;
code = element.textContent || '';
}
this.logDebug('从 PRE 标签提取,使用 innerText');
this.logDebug('从 PRE 标签提取');
}
// 记录原始提取的代码(调试用)
@@ -5336,6 +5193,63 @@ void 0;
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;
},
// ---------- 渲染引擎 ----------
/**
@@ -5358,88 +5272,90 @@ void 0;
return;
}
// 检测所有代码块(一次 DOM 遍历)
const blocks = this.detectMermaidBlocks();
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;
}
this.logDebug(`准备批量渲染 ${blocks.length} 个图表`);
// 需求 18.4: 将图表分为视口内和视口外两组
const visibleBlocks = [];
const invisibleBlocks = [];
blocks.forEach(block => {
if (this.isInViewport(block)) {
visibleBlocks.push(block);
} else {
invisibleBlocks.push(block);
}
});
this.logDebug(`视口内图表: ${visibleBlocks.length}, 视口外图表: ${invisibleBlocks.length}`);
// 优先渲染视口内的图表
let currentIndex = 0;
const batchSize = 3; // 每批渲染 3 个图表
const renderBatch = (blockList, isVisible) => {
if (currentIndex >= blockList.length) {
// 当前列表渲染完成
if (isVisible && invisibleBlocks.length > 0) {
// 视口内图表渲染完成,开始渲染视口外图表
this.logDebug('视口内图表渲染完成,开始渲染视口外图表');
currentIndex = 0;
requestAnimationFrame(() => renderBatch(invisibleBlocks, false));
} else {
this.logDebug(`完成渲染 ${blocks.length} 个图表`);
}
return;
}
const endIndex = Math.min(currentIndex + batchSize, blockList.length);
// 渲染当前批次
for (let i = currentIndex; i < endIndex; i++) {
const globalIndex = blocks.indexOf(blockList[i]);
this.renderChart(blockList[i], globalIndex);
}
currentIndex = endIndex;
// 继续下一批
requestAnimationFrame(() => renderBatch(blockList, isVisible));
};
// 开始渲染(优先渲染视口内图表)
if (visibleBlocks.length > 0) {
requestAnimationFrame(() => renderBatch(visibleBlocks, true));
} else if (invisibleBlocks.length > 0) {
// 如果没有视口内图表,直接渲染视口外图表
requestAnimationFrame(() => renderBatch(invisibleBlocks, false));
}
},
/**
* 检查元素是否在视口内
* @param {HTMLElement} element - 要检查的元素
* @returns {boolean} 是否在视口内
*/
isInViewport(element) {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
// 元素至少部分可见
return (
rect.top < windowHeight &&
rect.bottom > 0 &&
rect.left < windowWidth &&
rect.right > 0
);
startRender(blocks);
},
/**
@@ -5472,7 +5388,8 @@ void 0;
}
// 提取代码
const code = this.extractMermaidCode(element);
const rawCode = this.extractMermaidCode(element);
const code = rawCode;
if (!code) {
this.logDebug(`代码块为空,跳过: ${chartId}`);
@@ -5513,45 +5430,65 @@ void 0;
return;
}
// 渲染图表
const renderPromise = window.mermaid.render(`mermaid-svg-${chartId}`, code);
// 检查返回值是否是 Promise
if (!renderPromise || typeof renderPromise.then !== 'function') {
this.logError('Mermaid.render 没有返回 Promise可能是版本不兼容');
// 尝试使用旧版 API
this.renderChartLegacy(loadingContainer, code, container, chartId);
return;
}
renderPromise.then(result => {
// 渲染成功
container.innerHTML = result.svg;
// 保存原始代码(用于主题切换时重新渲染)
const onRenderSuccess = (svg, bindFunctions) => {
container.innerHTML = svg;
container.dataset.mermaidCode = code;
container.dataset.currentTheme = this.getMermaidTheme();
// 需求 3.5: 添加渲染状态标记,避免重复渲染
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}`);
}).catch(error => {
};
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: 渲染失败时记录错误但不抛出异常
this.logError(`图表渲染失败: ${chartId}`, error);
this.handleRenderError(loadingContainer, error, code);
});
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: 捕获所有异常,确保不影响其他图表
@@ -5613,6 +5550,9 @@ void 0;
}
} catch (error) {
this.logError('旧版 API 渲染失败', error);
if (this.tryUpgradeMermaidLibrary(element, code)) {
return;
}
this.handleRenderError(element, error, code);
}
},

View File

@@ -2824,7 +2824,6 @@ function argon_comment_text_render($text){
return argon_apply_comment_macros($text);
}
add_filter('comment_text', 'argon_comment_text_render', 9);
add_filter('the_content', 'argon_comment_text_render', 9);
//评论发送处理
function post_comment_preprocessing($comment){
@@ -4612,6 +4611,19 @@ function shortcode_mermaid($attr,$content=""){
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 "";
@@ -6422,7 +6434,33 @@ function argon_create_ai_query_log_table() {
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
add_action('after_switch_theme', 'argon_create_ai_query_log_table');
function argon_ai_query_log_table_exists() {
global $wpdb;
$table_name = $wpdb->prefix . 'argon_ai_query_log';
$found = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name));
return $found === $table_name;
}
function argon_maybe_create_ai_query_log_table() {
static $ran = false;
if ($ran) return;
$ran = true;
$option_key = 'argon_ai_query_log_table_version';
$current_version = 1;
$saved_version = intval(get_option($option_key, 0));
if ($saved_version === $current_version && argon_ai_query_log_table_exists()) {
return;
}
argon_create_ai_query_log_table();
update_option($option_key, $current_version);
}
add_action('after_switch_theme', 'argon_maybe_create_ai_query_log_table');
add_action('init', 'argon_maybe_create_ai_query_log_table', 5);
/**
* 记录 AI 查询
@@ -6442,6 +6480,8 @@ function argon_log_ai_query($provider, $model, $scenario, $prompt_length, $conte
global $wpdb;
$table_name = $wpdb->prefix . 'argon_ai_query_log';
argon_maybe_create_ai_query_log_table();
$wpdb->insert(
$table_name,
[
@@ -6490,6 +6530,10 @@ function argon_ai_query($scenario, $prompt, $content, $context = []) {
// 优先使用场景化的 API 配置(新系统)
$config = null;
$provider = '';
$config_scenario = $scenario;
if ($scenario === 'spam_detection' || $scenario === 'keyword_extraction') {
$config_scenario = 'spam';
}
// 如果 context 中指定了 provider使用指定的 provider
if (isset($context['provider'])) {
@@ -6497,19 +6541,19 @@ function argon_ai_query($scenario, $prompt, $content, $context = []) {
$config = argon_get_ai_provider_config($provider);
} else {
// 否则根据场景获取活动的 API 配置
$config = argon_get_active_api_config($scenario);
if ($config && isset($config['provider'])) {
$config = argon_get_active_api_config($config_scenario);
if ($config && !empty($config['provider'])) {
$provider = $config['provider'];
}
}
// 如果新系统没有配置,回退到旧系统
if (!$config || !isset($config['api_key'])) {
if (!$config || empty($provider) || empty($config['api_key'])) {
$provider = get_option('argon_ai_summary_provider', 'openai');
$config = argon_get_ai_provider_config($provider);
}
if (!$config || !isset($config['api_key'])) {
if (!$config || empty($provider) || empty($config['api_key'])) {
error_log("Argon AI Query Error: Provider config not found for {$provider}");
return false;
}
@@ -6530,6 +6574,31 @@ function argon_ai_query($scenario, $prompt, $content, $context = []) {
// 获取 API 端点
$endpoint = isset($config['api_endpoint']) ? $config['api_endpoint'] : '';
if (empty($model) || $provider === 'xiaomi') {
$provider_defaults = [
'openai' => 'gpt-4o-mini',
'anthropic' => 'claude-3-5-haiku-20241022',
'deepseek' => 'deepseek-chat',
'qianwen' => 'qwen-turbo',
'wenxin' => 'ernie-4.0-turbo-8k',
'doubao' => 'doubao-pro-32k',
'kimi' => 'moonshot-v1-8k',
'zhipu' => 'glm-4-flash',
'siliconflow' => 'Qwen/Qwen2.5-7B-Instruct',
'xiaomi' => 'MiMo-V2-Flash'
];
if (empty($model) && isset($provider_defaults[$provider])) {
$model = $provider_defaults[$provider];
}
if ($provider === 'xiaomi' && !empty($endpoint) && !empty($model)) {
if (strpos($endpoint, 'xiaomimimo.com') !== false && strcasecmp($model, 'MiMo-V2-Flash') === 0) {
$model = 'mimo-v2-flash';
} elseif (strpos($endpoint, 'api.mimo.xiaomi.com') !== false && strcasecmp($model, 'mimo-v2-flash') === 0) {
$model = 'MiMo-V2-Flash';
}
}
}
try {
switch ($provider) {
case 'openai':
@@ -6619,6 +6688,91 @@ function argon_ai_query($scenario, $prompt, $content, $context = []) {
return $result;
}
function argon_resolve_ai_provider_model($scenario, $context = []) {
$config = null;
$provider = '';
$config_scenario = $scenario;
if ($scenario === 'spam_detection' || $scenario === 'keyword_extraction') {
$config_scenario = 'spam';
}
if (is_array($context) && isset($context['provider'])) {
$provider = $context['provider'];
$config = argon_get_ai_provider_config($provider);
} else {
$config = argon_get_active_api_config($config_scenario);
if ($config && !empty($config['provider'])) {
$provider = $config['provider'];
}
}
if (!$config || empty($provider) || empty($config['api_key'])) {
$provider = get_option('argon_ai_summary_provider', 'openai');
$config = argon_get_ai_provider_config($provider);
}
$endpoint = is_array($config) && isset($config['api_endpoint']) ? $config['api_endpoint'] : '';
$model = '';
if (is_array($context) && isset($context['model'])) {
$model = $context['model'];
} elseif (is_array($config) && isset($config['model'])) {
$model = $config['model'];
}
$provider_defaults = [
'openai' => 'gpt-4o-mini',
'anthropic' => 'claude-3-5-haiku-20241022',
'deepseek' => 'deepseek-chat',
'qianwen' => 'qwen-turbo',
'wenxin' => 'ernie-4.0-turbo-8k',
'doubao' => 'doubao-pro-32k',
'kimi' => 'moonshot-v1-8k',
'zhipu' => 'glm-4-flash',
'siliconflow' => 'Qwen/Qwen2.5-7B-Instruct',
'xiaomi' => 'MiMo-V2-Flash'
];
if (empty($model) && isset($provider_defaults[$provider])) {
$model = $provider_defaults[$provider];
}
if ($provider === 'xiaomi' && !empty($endpoint) && !empty($model)) {
if (strpos($endpoint, 'xiaomimimo.com') !== false && strcasecmp($model, 'MiMo-V2-Flash') === 0) {
$model = 'mimo-v2-flash';
} elseif (strpos($endpoint, 'api.mimo.xiaomi.com') !== false && strcasecmp($model, 'mimo-v2-flash') === 0) {
$model = 'MiMo-V2-Flash';
}
}
return [
'provider' => $provider,
'model' => $model
];
}
function argon_get_latest_ai_query_provider_model($scenario, $post_id = 0, $comment_id = 0) {
global $wpdb;
$table_name = $wpdb->prefix . 'argon_ai_query_log';
$where = ['scenario = %s', "status = 'success'"];
$params = [$scenario];
if (!empty($post_id)) {
$where[] = 'post_id = %d';
$params[] = intval($post_id);
}
if (!empty($comment_id)) {
$where[] = 'comment_id = %d';
$params[] = intval($comment_id);
}
$sql = "SELECT provider, model FROM {$table_name} WHERE " . implode(' AND ', $where) . " ORDER BY id DESC LIMIT 1";
$row = $wpdb->get_row($wpdb->prepare($sql, $params), ARRAY_A);
if (is_array($row) && !empty($row['provider']) && isset($row['model'])) {
return $row;
}
return null;
}
/**
* 获取 AI 查询统计信息
*
@@ -6960,6 +7114,25 @@ function argon_get_ai_summary($post_id) {
// 如果缓存存在且内容未变化,返回缓存
if (!empty($cached_summary) && $cached_hash === $current_hash) {
$sync_key = 'argon_ai_summary_provider_model_synced_' . $post_id;
if (get_transient($sync_key) === false) {
$latest = argon_get_latest_ai_query_provider_model('summary', $post_id, 0);
if ($latest) {
$current_provider = get_post_meta($post_id, '_argon_ai_summary_provider', true);
$current_model = get_post_meta($post_id, '_argon_ai_summary_model', true);
if (!empty($latest['provider']) && $latest['provider'] !== $current_provider) {
update_post_meta($post_id, '_argon_ai_summary_provider', $latest['provider']);
}
if (isset($latest['model']) && $latest['model'] !== $current_model) {
update_post_meta($post_id, '_argon_ai_summary_model', $latest['model']);
}
set_transient($sync_key, 1, DAY_IN_SECONDS);
} else {
set_transient($sync_key, 1, 10 * MINUTE_IN_SECONDS);
}
}
return $cached_summary;
}
@@ -7245,7 +7418,7 @@ function argon_log_ai_error($provider, $error_type, $error_message, $post_id = 0
* @param WP_Post $post 文章对象
* @return string|false 摘要内容或 false
*/
function argon_generate_ai_summary($post) {
function argon_generate_ai_summary($post, $ai_context = []) {
// 准备文章内容
$content = wp_strip_all_tags($post->post_content);
$content = preg_replace('/\s+/', ' ', $content);
@@ -7265,10 +7438,10 @@ function argon_generate_ai_summary($post) {
$prompt = get_option('argon_ai_summary_prompt', '你是一个专业的内容摘要助手。请仔细阅读以下文章内容用简洁、准确的语言总结文章的核心观点和主要内容。要求1) 控制在 100-150 字以内2) 突出文章的关键信息和亮点3) 使用通俗易懂的语言4) 保持客观中立的语气。');
// 使用统一的 AI 查询接口
$result = argon_ai_query('summary', $prompt, $content, [
$result = argon_ai_query('summary', $prompt, $content, array_merge([
'post_id' => $post->ID,
'user_id' => get_current_user_id()
]);
], is_array($ai_context) ? $ai_context : []));
// 检查结果
if ($result === false) {
@@ -7892,6 +8065,17 @@ function argon_check_ai_summary() {
delete_transient('argon_ai_summary_generating_' . $post_id);
$model = get_post_meta($post_id, '_argon_ai_summary_model', true);
$provider = get_post_meta($post_id, '_argon_ai_summary_provider', true);
$latest = argon_get_latest_ai_query_provider_model('summary', $post_id, 0);
if ($latest) {
if (!empty($latest['provider']) && $latest['provider'] !== $provider) {
$provider = $latest['provider'];
update_post_meta($post_id, '_argon_ai_summary_provider', $provider);
}
if (isset($latest['model']) && $latest['model'] !== $model) {
$model = $latest['model'];
update_post_meta($post_id, '_argon_ai_summary_model', $model);
}
}
$code = get_post_meta($post_id, '_argon_ai_summary_code', true);
// 如果没有识别码,生成一个
@@ -7918,16 +8102,34 @@ function argon_check_ai_summary() {
// 触发生成
$post = get_post($post_id);
if ($post) {
$summary = argon_generate_ai_summary($post);
$resolved = argon_resolve_ai_provider_model('summary', [
'post_id' => $post_id,
'user_id' => get_current_user_id()
]);
$provider = isset($resolved['provider']) ? $resolved['provider'] : '';
$model = isset($resolved['model']) ? $resolved['model'] : '';
$summary = argon_generate_ai_summary($post, [
'provider' => $provider,
'model' => $model
]);
if ($summary !== false) {
$current_hash = md5($post->post_content . $post->post_title);
$provider = get_option('argon_ai_summary_provider', 'openai');
$model = get_option('argon_ai_summary_model', '');
// 生成唯一识别码
$summary_code = argon_generate_summary_code();
$latest = argon_get_latest_ai_query_provider_model('summary', $post_id, 0);
if ($latest) {
if (!empty($latest['provider'])) {
$provider = $latest['provider'];
}
if (isset($latest['model'])) {
$model = $latest['model'];
}
}
// 保存摘要和模型信息
update_post_meta($post_id, '_argon_ai_summary', $summary);
update_post_meta($post_id, '_argon_ai_summary_hash', $current_hash);
@@ -9789,11 +9991,21 @@ function argon_detect_spam_comment_sync($comment) {
// 构建评论上下文信息
$comment_text = argon_build_comment_context($comment);
$resolved = argon_resolve_ai_provider_model('spam_detection', [
'comment_id' => $comment->comment_ID,
'post_id' => $comment->comment_post_ID,
'user_id' => $comment->user_id
]);
$provider = isset($resolved['provider']) ? $resolved['provider'] : '';
$model = isset($resolved['model']) ? $resolved['model'] : '';
// 使用统一的 AI 查询接口
$result_text = argon_ai_query('spam_detection', $prompt, $comment_text, [
'comment_id' => $comment->comment_ID,
'post_id' => $comment->comment_post_ID,
'user_id' => $comment->user_id
'user_id' => $comment->user_id,
'provider' => $provider,
'model' => $model
]);
if ($result_text === false) {
@@ -9804,6 +10016,15 @@ function argon_detect_spam_comment_sync($comment) {
$result = json_decode($result_text, true);
if ($result && isset($result['content_spam'])) {
$latest = argon_get_latest_ai_query_provider_model('spam_detection', 0, $comment->comment_ID);
if ($latest) {
if (!empty($latest['provider'])) {
$provider = $latest['provider'];
}
if (isset($latest['model'])) {
$model = $latest['model'];
}
}
// 转换为统一格式
$unified_result = [
'is_spam' => $result['content_spam'],
@@ -9818,6 +10039,8 @@ function argon_detect_spam_comment_sync($comment) {
// 保存检测结果
update_comment_meta($comment->comment_ID, '_argon_spam_detection_result', $unified_result);
update_comment_meta($comment->comment_ID, '_argon_spam_detection_time', time());
update_comment_meta($comment->comment_ID, '_argon_spam_detection_provider', $provider);
update_comment_meta($comment->comment_ID, '_argon_spam_detection_model', $model);
return $unified_result;
}
@@ -10246,6 +10469,17 @@ function argon_async_spam_detection_handler($comment_id) {
$detection_code = argon_generate_detection_code($comment_id);
update_comment_meta($comment_id, '_argon_spam_detection_code', $detection_code);
$config = argon_get_active_api_config('spam');
if (!empty($config) && !empty($config['api_key']) && !empty($config['provider'])) {
update_comment_meta($comment_id, '_argon_spam_detection_provider', $config['provider']);
update_comment_meta($comment_id, '_argon_spam_detection_model', isset($config['model']) ? $config['model'] : '');
} else {
$provider = get_option('argon_ai_summary_provider', 'openai');
$provider_config = argon_get_ai_provider_config($provider);
update_comment_meta($comment_id, '_argon_spam_detection_provider', $provider);
update_comment_meta($comment_id, '_argon_spam_detection_model', !empty($provider_config['model']) ? $provider_config['model'] : get_option('argon_ai_summary_model', ''));
}
if ($result && isset($result['is_spam'])) {
$content_spam = $result['is_spam'];
$username_invalid = isset($result['username_invalid']) ? $result['username_invalid'] : false;
@@ -10501,12 +10735,24 @@ function argon_spam_detection_scan() {
$spam_results = [];
$checked_ids = [];
$config = argon_get_active_api_config('spam');
if (!empty($config) && !empty($config['api_key']) && !empty($config['provider'])) {
$provider = $config['provider'];
$model = isset($config['model']) ? $config['model'] : '';
} else {
$provider = get_option('argon_ai_summary_provider', 'openai');
$provider_config = argon_get_ai_provider_config($provider);
$model = !empty($provider_config['model']) ? $provider_config['model'] : get_option('argon_ai_summary_model', '');
}
foreach ($result as $item) {
$comment_id = $item['id'];
$checked_ids[] = $comment_id;
// 记录检测时间
update_comment_meta($comment_id, '_argon_spam_detection_time', time());
update_comment_meta($comment_id, '_argon_spam_detection_provider', $provider);
update_comment_meta($comment_id, '_argon_spam_detection_model', $model);
// 生成识别码
$detection_code = argon_generate_detection_code($comment_id);
@@ -10555,6 +10801,8 @@ function argon_spam_detection_scan() {
foreach ($comments_data as $comment_data) {
if (!in_array($comment_data['id'], $checked_ids)) {
update_comment_meta($comment_data['id'], '_argon_spam_detection_time', time());
update_comment_meta($comment_data['id'], '_argon_spam_detection_provider', $provider);
update_comment_meta($comment_data['id'], '_argon_spam_detection_model', $model);
$detection_code = argon_generate_detection_code($comment_data['id']);
update_comment_meta($comment_data['id'], '_argon_spam_detection_code', $detection_code);
}
@@ -10970,11 +11218,8 @@ function argon_update_mermaid_settings($settings) {
/**
* 检测页面内容是否包含 Mermaid 代码块
*
* 支持多种格式:
* - <div class="mermaid">
* - <pre><code class="language-mermaid">
* - <pre data-lang="mermaid">
* - <code class="mermaid">
* 支持 Shortcode 格式:
* - [mermaid]...[/mermaid]
*
* @param string $content 页面内容
* @return bool 是否包含 Mermaid 代码块
@@ -10986,11 +11231,7 @@ function argon_has_mermaid_content($content) {
// 检测多种 Mermaid 代码块格式
$patterns = [
'/<div[^>]*class=["\']([^"\']*\s)?mermaid(\s[^"\']*)?["\'][^>]*>/i', // <div class="mermaid">
'/<code[^>]*class=["\']([^"\']*\s)?language-mermaid(\s[^"\']*)?["\'][^>]*>/i', // <code class="language-mermaid">
'/<pre[^>]*data-lang=["\']mermaid["\'][^>]*>/i', // <pre data-lang="mermaid">
'/<code[^>]*class=["\']([^"\']*\s)?mermaid(\s[^"\']*)?["\'][^>]*>/i', // <code class="mermaid">
'/:::\s*mermaid/i' // ::: mermaid (Markdown 容器语法)
'/\[mermaid[^\]]*\]/i'
];
foreach ($patterns as $pattern) {

View File

@@ -92,13 +92,17 @@
<?php
$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(get_the_content('...')), $trim_words_count);
$preview = wp_trim_words(do_shortcode($content_for_preview), $trim_words_count);
}else{
$preview = wp_trim_words(get_the_content('...'), $trim_words_count);
$preview = wp_trim_words($content_for_preview, $trim_words_count);
}

View File

@@ -32,13 +32,17 @@
<?php
$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(get_the_content('...')), $trim_words_count);
$preview = wp_trim_words(do_shortcode($content_for_preview), $trim_words_count);
}else{
$preview = wp_trim_words(get_the_content('...'), $trim_words_count);
$preview = wp_trim_words($content_for_preview, $trim_words_count);
}

View File

@@ -84,13 +84,17 @@
<?php
$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(get_the_content('...')), $trim_words_count);
$preview = wp_trim_words(do_shortcode($content_for_preview), $trim_words_count);
}else{
$preview = wp_trim_words(get_the_content('...'), $trim_words_count);
$preview = wp_trim_words($content_for_preview, $trim_words_count);
}