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 编译通过。
51 lines
1.6 KiB
TypeScript
51 lines
1.6 KiB
TypeScript
/**
|
||
* 代码块 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);
|
||
}
|