优化公式渲染,添加mermaid图渲染支持 #817

Merged
Eeeros merged 2 commits from Eeeros/forgeplus-react:gitlink_ssr_head into gitlink_ssr_head 2026-01-22 16:09:01 +08:00
6 changed files with 1656 additions and 5 deletions

View File

@ -54,6 +54,7 @@
<script src="%PUBLIC_URL%js/alex/moment.js"></script>
<script src="https://gw.alipayobjects.com/os/lib/alipay/alex/2.0.19/bundle/alex.all.global.min.js"></script>
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script src="%PUBLIC_URL%js/editormd/mermaid.min.js"></script>
<%= htmlWebpackPlugin.tags.bodyTags %>
<script>
var _hmt = _hmt || [];

View File

@ -1921,7 +1921,7 @@
} catch (error) {
renderString = `<p style="color:#cc0000" title="${ error.message }">${ token.raw }`
}
return renderString
return `<p class='editormd-tex'>${renderString}</p>`
}
};
@ -3487,6 +3487,9 @@
else if (lang === "math" || lang === "latex" || lang === "katex") {
return "<p class=\"" + editormd.classNames.tex + "\">" + code + "</p>";
}
else if (code.match(/^sequenceDiagram/) || code.match(/^graph/)) {
return '<pre class="mermaid">' + code + '</pre>';
}
else {
return marked.Renderer.prototype.code.apply(this, arguments);

1580
public/js/editormd/mermaid.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -145,6 +145,8 @@ renderer.code = function (code, infostring, escaped) {
if (['latex', 'katex', 'math'].indexOf(lang) >= 0) {
return `<p class='editormd-tex'>${code}</p>`
} else if (code.match(/^sequenceDiagram/) || code.match(/^graph/) || lang === 'mermaid') {
return '<pre class="mermaid">' + code + '</pre>';
} else {
return `<pre class="prettyprint linenums"><code class="language-${infostring}">${escaped ? code : escape(code, true)}</code></pre>\n`
}
@ -228,8 +230,9 @@ const kateX = {
renderer(token) {
let renderString = ''
try {
renderString = renderToString(token.text, {
renderString = renderToString(token.text || '', {
displayMode: token.displayMode,
throwOnError: false,
macros: {
"\\mbox": "\\text{#1}",
"\\textrm": "\\text{#1}",
@ -240,7 +243,7 @@ const kateX = {
} catch (error) {
renderString = `<p style="color:#cc0000" title="${ error.message }">${ token.raw }`
}
return renderString
return `<p class='editormd-tex'>${renderString}</p>`
}
};

34
src/common/mermaid.js Normal file
View File

@ -0,0 +1,34 @@
let mermaidPromise = null;
export function loadMermaid() {
if (mermaidPromise) {
return mermaidPromise; // 防止重复加载
}
// 如果已经存在
if (typeof window !== 'undefined' && window.mermaid) {
return Promise.resolve(window.mermaid);
}
mermaidPromise = new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = `${process.env.PUBLIC_URL}/js/editormd/mermaid.min.js`;
script.async = true;
script.onload = () => {
if (window.mermaid) {
resolve(window.mermaid);
} else {
reject(new Error('Mermaid failed to load'));
}
};
script.onerror = () => {
reject(new Error('Failed to load Mermaid script'));
};
document.head.appendChild(script);
});
return mermaidPromise;
}

View File

@ -9,6 +9,7 @@ import axios from 'axios';
import imgError from '../images/img-error.png'
import { renderToString } from 'katex'
import { loadMermaid } from '../common/mermaid';
const preRegex = /<pre[^>]*>/g;
const strRegexSub = /:([a-zA-Z_]+):/g;
@ -37,6 +38,35 @@ export default ({
}
},[owner,projectsId])
useEffect(() => {
if (!html || !el.current) return;
const hasMermaid = html.includes('<pre class="mermaid">');
if (!hasMermaid) return;
loadMermaid()
.then((mermaid) => {
//
if (!window.mermaidInitialized) {
mermaid.initialize({
startOnLoad: false, //
theme: 'default',
});
window.mermaidInitialized = true;
}
try {
// init .mermaid
mermaid.init(undefined, el.current.querySelectorAll('.mermaid'));
} catch (e) {
console.error('Mermaid render error:', e);
}
})
.catch(err => {
console.error('Failed to load Mermaid:', err);
});
}, [html]);
function getIssueList(){
let matchQuote = str.match(quoteRegex);
let array = [];
@ -100,8 +130,8 @@ export default ({
}
}
rs = rs.replace(/(__special_katext_id_\d+__)/g, (_match, capture) => {
const { type, expression } = math_expressions[capture];
return renderToString(_unescape(expression) || '', { displayMode: true, throwOnError: false,macros: {
const { type, expression } = math_expressions[capture];
return renderToString(_unescape(expression) || '', { displayMode: type === 'block', throwOnError: false,macros: {
"\\mbox": "\\text{#1}",
"\\textrm": "\\text{#1}",
"\\textnormal": "\\text{#1}",