feat: 集成KaTeX库支持LaTeX数学公式渲染

This commit is contained in:
lixinran 2025-09-10 15:53:16 +08:00
parent b5c56d4946
commit 682744e4b8
9 changed files with 14653 additions and 25 deletions

Binary file not shown.

View File

@ -1,71 +1,77 @@
{ {
"hash": "1229e8fa", "hash": "76772e52",
"browserHash": "5c2510fa", "browserHash": "b5df73c3",
"optimized": { "optimized": {
"axios": { "axios": {
"src": "../../axios/index.js", "src": "../../axios/index.js",
"file": "axios.js", "file": "axios.js",
"fileHash": "c14dff39", "fileHash": "d5a6fb13",
"needsInterop": false "needsInterop": false
}, },
"mammoth": { "mammoth": {
"src": "../../mammoth/lib/index.js", "src": "../../mammoth/lib/index.js",
"file": "mammoth.js", "file": "mammoth.js",
"fileHash": "9331e90f", "fileHash": "8e0b13e7",
"needsInterop": true "needsInterop": true
}, },
"marked": { "marked": {
"src": "../../marked/lib/marked.esm.js", "src": "../../marked/lib/marked.esm.js",
"file": "marked.js", "file": "marked.js",
"fileHash": "d9459e29", "fileHash": "330cfdd3",
"needsInterop": false "needsInterop": false
}, },
"pdfjs-dist": { "pdfjs-dist": {
"src": "../../pdfjs-dist/build/pdf.mjs", "src": "../../pdfjs-dist/build/pdf.mjs",
"file": "pdfjs-dist.js", "file": "pdfjs-dist.js",
"fileHash": "4db246e9", "fileHash": "63546997",
"needsInterop": false "needsInterop": false
}, },
"prismjs": { "prismjs": {
"src": "../../prismjs/prism.js", "src": "../../prismjs/prism.js",
"file": "prismjs.js", "file": "prismjs.js",
"fileHash": "e122bdaf", "fileHash": "3730e60a",
"needsInterop": true "needsInterop": true
}, },
"prismjs/components/prism-css": { "prismjs/components/prism-css": {
"src": "../../prismjs/components/prism-css.js", "src": "../../prismjs/components/prism-css.js",
"file": "prismjs_components_prism-css.js", "file": "prismjs_components_prism-css.js",
"fileHash": "b1805788", "fileHash": "b5dc8638",
"needsInterop": true "needsInterop": true
}, },
"prismjs/components/prism-javascript": { "prismjs/components/prism-javascript": {
"src": "../../prismjs/components/prism-javascript.js", "src": "../../prismjs/components/prism-javascript.js",
"file": "prismjs_components_prism-javascript.js", "file": "prismjs_components_prism-javascript.js",
"fileHash": "c034dce0", "fileHash": "a3fb501c",
"needsInterop": true "needsInterop": true
}, },
"prismjs/components/prism-json": { "prismjs/components/prism-json": {
"src": "../../prismjs/components/prism-json.js", "src": "../../prismjs/components/prism-json.js",
"file": "prismjs_components_prism-json.js", "file": "prismjs_components_prism-json.js",
"fileHash": "1379480f", "fileHash": "2d44f86b",
"needsInterop": true "needsInterop": true
}, },
"prismjs/components/prism-python": { "prismjs/components/prism-python": {
"src": "../../prismjs/components/prism-python.js", "src": "../../prismjs/components/prism-python.js",
"file": "prismjs_components_prism-python.js", "file": "prismjs_components_prism-python.js",
"fileHash": "fa615091", "fileHash": "ca5259af",
"needsInterop": true "needsInterop": true
}, },
"prismjs/components/prism-sql": { "prismjs/components/prism-sql": {
"src": "../../prismjs/components/prism-sql.js", "src": "../../prismjs/components/prism-sql.js",
"file": "prismjs_components_prism-sql.js", "file": "prismjs_components_prism-sql.js",
"fileHash": "31352c1e", "fileHash": "4a89443c",
"needsInterop": true "needsInterop": true
}, },
"vue": { "vue": {
"src": "../../vue/dist/vue.runtime.esm-bundler.js", "src": "../../vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js", "file": "vue.js",
"fileHash": "7f03e6bd", "fileHash": "a7d38f14",
"needsInterop": false
},
"katex": {
"src": "../../katex/dist/katex.mjs",
"file": "katex.js",
"fileHash": "d08a8bdf",
"needsInterop": false "needsInterop": false
} }
}, },

14541
frontend/node_modules/.vite/deps/katex.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

7
frontend/node_modules/.vite/deps/katex.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"axios": "^1.5.0", "axios": "^1.5.0",
"katex": "^0.16.22",
"mammoth": "^1.10.0", "mammoth": "^1.10.0",
"marked": "^16.2.1", "marked": "^16.2.1",
"markmap-lib": "^0.18.12", "markmap-lib": "^0.18.12",

View File

@ -9,6 +9,7 @@
}, },
"dependencies": { "dependencies": {
"axios": "^1.5.0", "axios": "^1.5.0",
"katex": "^0.16.22",
"mammoth": "^1.10.0", "mammoth": "^1.10.0",
"marked": "^16.2.1", "marked": "^16.2.1",
"markmap-lib": "^0.18.12", "markmap-lib": "^0.18.12",

View File

@ -710,6 +710,28 @@ Level 4 标题用 #####
// Markdown // Markdown
const formatMarkdownToText = (markdown) => { const formatMarkdownToText = (markdown) => {
//
if (markdown.includes('|') && markdown.includes('-')) {
const lines = markdown.split('\n');
let hasTableRow = false;
let hasSeparator = false;
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine.includes('|') && trimmedLine.split('|').length >= 3) {
hasTableRow = true;
}
if (trimmedLine.includes('|') && trimmedLine.includes('-') && /^[\s\|\-\:]+$/.test(trimmedLine)) {
hasSeparator = true;
}
}
if (hasTableRow && hasSeparator) {
console.log('🚫 formatMarkdownToText: 检测到表格内容,跳过转换');
return markdown; //
}
}
return markdown return markdown
// //
.replace(/^### (.*$)/gim, '📋 $1') // .replace(/^### (.*$)/gim, '📋 $1') //

View File

@ -3340,9 +3340,9 @@ const updateMindMapRealtime = async (data, title) => {
maxScale: 5, maxScale: 5,
minScale: 0.1, minScale: 0.1,
markdown: (text, nodeObj) => { markdown: (text, nodeObj) => {
console.log('🔍 实时更新 Mind Elixir markdown函数被调用:', text.substring(0, 100) + '...'); // console.log('🔍 Mind Elixir markdown:', text.substring(0, 100) + '...');
// markdown // markdown
if (text.includes('|') || text.includes('**') || text.includes('`') || text.includes('#')) { if (text.includes('|') || text.includes('**') || text.includes('`') || text.includes('#') || text.includes('$')) {
console.log('🎨 实时更新 检测到markdown内容开始渲染:', text.substring(0, 100) + '...'); console.log('🎨 实时更新 检测到markdown内容开始渲染:', text.substring(0, 100) + '...');
const result = smartRenderNodeContent(text); const result = smartRenderNodeContent(text);
console.log('🎨 实时更新 渲染结果:', result.substring(0, 200) + '...'); console.log('🎨 实时更新 渲染结果:', result.substring(0, 200) + '...');

View File

@ -6,11 +6,13 @@
import { marked } from 'marked'; import { marked } from 'marked';
import Prism from 'prismjs'; import Prism from 'prismjs';
import katex from 'katex';
import 'prismjs/components/prism-javascript'; import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-css'; import 'prismjs/components/prism-css';
import 'prismjs/components/prism-json'; import 'prismjs/components/prism-json';
import 'prismjs/components/prism-python'; import 'prismjs/components/prism-python';
import 'prismjs/components/prism-sql'; import 'prismjs/components/prism-sql';
import 'katex/dist/katex.min.css';
// 配置marked选项 // 配置marked选项
marked.setOptions({ marked.setOptions({
@ -52,9 +54,39 @@ export const renderMarkdownToHTML = (markdown) => {
*/ */
const preprocessMarkdown = (markdown) => { const preprocessMarkdown = (markdown) => {
return markdown return markdown
// 处理数学公式(如果需要的话) // 处理块级数学公式($$...$$
.replace(/\$\$(.*?)\$\$/g, '<div class="math-block">$$$1$$</div>') .replace(/\$\$([\s\S]*?)\$\$/g, (match, formula) => {
.replace(/\$(.*?)\$/g, '<span class="math-inline">$$1$</span>'); try {
const rendered = katex.renderToString(formula.trim(), {
displayMode: true,
throwOnError: false
});
return `<div class="math-block">${rendered}</div>`;
} catch (error) {
console.warn('数学公式渲染失败:', error);
return `<div class="math-error">数学公式错误: ${formula}</div>`;
}
})
// 处理行内数学公式($...$),但避免匹配单个$符号
.replace(/\$([^$\n]+?)\$/g, (match, formula) => {
// 检查是否包含LaTeX命令或数学符号
if (formula.includes('\\') || formula.includes('{') || formula.includes('}') ||
formula.includes('^') || formula.includes('_') || formula.includes('=') ||
formula.includes('+') || formula.includes('-') || formula.includes('*') ||
formula.includes('/') || formula.includes('(') || formula.includes(')')) {
try {
const rendered = katex.renderToString(formula.trim(), {
displayMode: false,
throwOnError: false
});
return `<span class="math-inline">${rendered}</span>`;
} catch (error) {
console.warn('数学公式渲染失败:', error);
return `<span class="math-error">数学公式错误: ${formula}</span>`;
}
}
return match; // 如果不包含数学符号,保持原样
});
}; };
/** /**
@ -67,10 +99,7 @@ const postprocessHTML = (html) => {
// 为表格添加样式类 // 为表格添加样式类
.replace(/<table>/g, '<table class="markdown-table">') .replace(/<table>/g, '<table class="markdown-table">')
// 为代码块添加样式类 // 为代码块添加样式类
.replace(/<pre><code/g, '<pre class="markdown-code"><code') .replace(/<pre><code/g, '<pre class="markdown-code"><code');
// 为数学公式添加样式类
.replace(/<div class="math-block">/g, '<div class="math-block markdown-math">')
.replace(/<span class="math-inline">/g, '<span class="math-inline markdown-math">');
// 创建临时DOM元素来处理语法高亮 // 创建临时DOM元素来处理语法高亮
const tempDiv = document.createElement('div'); const tempDiv = document.createElement('div');
@ -320,8 +349,27 @@ const addMarkdownStyles = (container) => {
font-style: italic; font-style: italic;
} }
.markdown-math { .math-block {
font-family: 'Times New Roman', serif; text-align: center;
margin: 4px 0;
padding: 4px;
background: #f8f9fa;
border-radius: 4px;
}
.math-inline {
display: inline;
margin: 0 2px;
}
.math-error {
color: #dc3545;
background: #f8d7da;
border: 1px solid #f5c6cb;
padding: 2px 4px;
border-radius: 3px;
font-size: 10px;
font-family: monospace;
} }
.markdown-error { .markdown-error {
@ -400,6 +448,8 @@ export const hasMarkdownSyntax = (content) => {
/^\s*\d+\.\s+/m, // 有序列表 /^\s*\d+\.\s+/m, // 有序列表
/\[.*?\]\(.*?\)/, // 链接 /\[.*?\]\(.*?\)/, // 链接
/!\[.*?\]\(.*?\)/, // 图片 /!\[.*?\]\(.*?\)/, // 图片
/\$\$.*?\$\$/, // 块级数学公式
/\$.*?\$/, // 行内数学公式
]; ];
return markdownPatterns.some(pattern => pattern.test(content)); return markdownPatterns.some(pattern => pattern.test(content));