68 lines
2.6 KiB
TypeScript
68 lines
2.6 KiB
TypeScript
|
|
import { codeBlockConfig } from '@milkdown/kit/component/code-block'
|
||
|
|
import mermaid from 'mermaid'
|
||
|
|
|
||
|
|
// ── Mermaid init ────────────────────────────────────────────────────────────
|
||
|
|
let mermaidReady = false
|
||
|
|
let diagramCounter = 0
|
||
|
|
|
||
|
|
function ensureMermaid() {
|
||
|
|
if (mermaidReady) return
|
||
|
|
const dark = window.matchMedia?.('(prefers-color-scheme: dark)').matches
|
||
|
|
mermaid.initialize({
|
||
|
|
startOnLoad: false,
|
||
|
|
theme: dark ? 'dark' : 'default',
|
||
|
|
securityLevel: 'loose',
|
||
|
|
fontFamily: 'inherit',
|
||
|
|
})
|
||
|
|
mermaidReady = true
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── renderPreview ───────────────────────────────────────────────────────────
|
||
|
|
// Pass this function to codeBlockConfig.renderPreview via crepe.editor.config().
|
||
|
|
// For non-mermaid languages, return null to use the default preview renderer.
|
||
|
|
|
||
|
|
export async function mermaidRenderPreview(
|
||
|
|
language: string,
|
||
|
|
content: string,
|
||
|
|
applyPreview: (value: null | string | HTMLElement) => void,
|
||
|
|
): Promise<void> {
|
||
|
|
if (language !== 'mermaid') {
|
||
|
|
applyPreview(null)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
ensureMermaid()
|
||
|
|
|
||
|
|
// Show a placeholder immediately
|
||
|
|
const wrapper = document.createElement('div')
|
||
|
|
wrapper.className = 'mermaid-block'
|
||
|
|
const inner = document.createElement('div')
|
||
|
|
inner.className = 'mermaid-inner'
|
||
|
|
inner.innerHTML = '<div class="mermaid-loading">···</div>'
|
||
|
|
wrapper.appendChild(inner)
|
||
|
|
applyPreview(wrapper)
|
||
|
|
|
||
|
|
const id = `mermaid-render-${++diagramCounter}`
|
||
|
|
const code = content.trim() || 'graph TD\nA-->B'
|
||
|
|
|
||
|
|
try {
|
||
|
|
const { svg } = await mermaid.render(id, code)
|
||
|
|
inner.innerHTML = svg
|
||
|
|
applyPreview(wrapper)
|
||
|
|
} catch (err) {
|
||
|
|
const pre = document.createElement('pre')
|
||
|
|
pre.className = 'mermaid-error'
|
||
|
|
pre.textContent = `Mermaid error:\n${err instanceof Error ? err.message : String(err)}`
|
||
|
|
inner.innerHTML = ''
|
||
|
|
inner.appendChild(pre)
|
||
|
|
applyPreview(wrapper)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Milkdown plugin helper ─────────────────────────────────────────────────
|
||
|
|
// Call this inside a crepe.editor.config() callback:
|
||
|
|
// ctx.update(codeBlockConfig.key, (prev) => ({ ...prev, renderPreview: mermaidRenderPreview }))
|
||
|
|
//
|
||
|
|
// Re-export the config key so callers don't need to import @milkdown/components directly.
|
||
|
|
export { codeBlockConfig }
|