yggdrasil/libs/yggdrasil-core/src/post-content.ts
xfy 70edc5e46c feat(frontend): mermaid 流程图懒加载渲染
mermaid 无官方 SSR 支持,采用客户端 IntersectionObserver 懒加载:服务端
markdown.rs 只产出普通 <pre><code class="language-mermaid"> 代码块,
前端在块进视口时动态 import 独立 bundle(~1MB)渲染成 SVG。

新增 libs/mermaid-renderer/:mermaid 11.16 IIFE bundle,输出 public/mermaid/
yggdrasil-core/mermaid.ts:扫描 language-mermaid 块,视口可见时动态 import
  /mermaid/mermaid.js(单例缓存),mermaid.initialize 适配 light/dark 主题,
  securityLevel=strict;渲染失败加 .mermaid-error class 保留源码;幂等守卫
post-content.ts:initCopyButtons 跳过 language-mermaid 块(图无需 copy)
post_content.rs:use_effect 调 __initMermaid('.post-content', theme),
  读 use_resolved_theme() 传主题并建立订阅

新增 6 个 mermaid 单测(扫描/主题/幂等/非mermaid/未命中/错误回退)。
550 Rust + 28 前端测试全过,双 target 编译通过。
2026-07-16 14:22:43 +08:00

51 lines
1.6 KiB
TypeScript
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.

/**
* 代码块 copy 按钮。
*
* 为 pre>code 注入一个 .copy-code 按钮,点击复制代码文本并回显 copied!。
* 2 秒后还原文案。样式见 input.css 的 .copy-code 规则(全局 Tailwind 构建)。
*/
function initCopyButtons(root: Element): void {
const blocks = root.querySelectorAll('pre > code');
blocks.forEach((code) => {
const pre = code.parentElement;
if (!pre) return;
if (pre.querySelector('.copy-code')) return;
// mermaid 代码块由 mermaid.ts 渲染成 SVG不注入 copy 按钮(图无需复制源码)。
if (code.classList.contains('language-mermaid')) return;
const btn = document.createElement('button');
btn.className = 'copy-code';
btn.textContent = 'copy';
btn.setAttribute('aria-label', 'Copy code');
const codeText = code.textContent || '';
btn.addEventListener('click', () => {
if (navigator.clipboard?.writeText) {
navigator.clipboard.writeText(codeText);
} else {
const ta = document.createElement('textarea');
ta.value = codeText;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
}
btn.textContent = 'copied!';
setTimeout(() => {
btn.textContent = 'copy';
}, 2000);
});
pre.appendChild(btn);
});
}
export function initPostContent(selector: string): void {
const root = document.querySelector(selector);
if (!root) return;
initCopyButtons(root);
}