Files
sproutblog/public/js/markdown-client.js
2026-04-15 13:20:51 +08:00

98 lines
2.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { marked } from "https://esm.sh/marked@15";
import markedKatex from "https://esm.sh/marked-katex-extension@5?deps=marked@15,katex@0.16";
import hljs from "https://esm.sh/highlight.js@11";
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
marked.use(
markedKatex({
throwOnError: false,
nonStandard: true
})
);
// breaks: true — 段内单换行渲染为 <br>,与 Obsidian 等编辑器的换行习惯一致CommonMark 默认会把单换行当成空格)
marked.setOptions({ gfm: true, breaks: true });
function decodeHtmlEntities(str) {
return String(str)
.replace(/&nbsp;/g, "\u00a0")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&#(\d+);/g, (_, n) => {
const code = Number(n);
return code >= 0 && code <= 0x10ffff ? String.fromCharCode(code) : "";
})
.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => {
const code = parseInt(h, 16);
return code >= 0 && code <= 0x10ffff ? String.fromCharCode(code) : "";
});
}
function escapeHtml(s) {
return String(s)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function mermaidFencedToDiv(html) {
return html.replace(
/<pre><code class="language-mermaid">([\s\S]*?)<\/code><\/pre>/gi,
(_, inner) => {
const raw = decodeHtmlEntities(inner);
return `<div class="mermaid">${escapeHtml(raw)}</div>`;
}
);
}
function decodeMdFromB64(b64) {
return decodeURIComponent(escape(atob(b64)));
}
async function renderOne(el) {
const b64 = el.getAttribute("data-b64-md");
if (!b64) return;
let md;
try {
md = decodeMdFromB64(b64);
} catch {
el.innerHTML = "<p>正文解码失败。</p>";
el.classList.remove("markdown-pending");
return;
}
el.removeAttribute("data-b64-md");
let html = marked.parse(md, { async: false });
if (typeof html !== "string") html = "";
html = mermaidFencedToDiv(html);
el.innerHTML = html;
el.classList.remove("markdown-pending");
el.querySelectorAll("pre code").forEach((block) => {
try {
hljs.highlightElement(block);
} catch {
/* ignore unknown languages */
}
});
const mNodes = el.querySelectorAll(".mermaid");
if (mNodes.length) {
mermaid.initialize({ startOnLoad: false, theme: "neutral" });
await mermaid.run({ nodes: mNodes });
}
}
function run() {
document.querySelectorAll(".markdown-body[data-b64-md]").forEach((el) => {
renderOne(el).catch(() => {
el.innerHTML = "<p>渲染失败。</p>";
el.classList.remove("markdown-pending");
});
});
}
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", run);
else run();