Files
byre/web/src/mermaid.ts
T

88 lines
2.5 KiB
TypeScript

import { errorText, h } from "./dom.js";
type Mermaid = {
initialize(config: Record<string, unknown>): void;
render(id: string, text: string): Promise<{ svg: string }>;
};
declare global {
interface Window {
mermaid?: Mermaid;
}
}
let loading: Promise<Mermaid> | undefined;
let next = 0;
function load(): Promise<Mermaid> {
loading ??= new Promise((resolve, reject) => {
const script = h("script", { src: "/vendor/mermaid.min.js" });
script.onload = () => {
const m = window.mermaid!;
const css = getComputedStyle(document.documentElement);
const v = (name: string) => css.getPropertyValue(name).trim();
m.initialize({
startOnLoad: false,
securityLevel: "strict",
suppressErrorRendering: true,
theme: "base",
fontFamily: v("--font"),
themeVariables: {
fontFamily: v("--font"),
fontSize: "13px",
background: v("--bg"),
primaryColor: v("--surface"),
primaryTextColor: v("--text"),
primaryBorderColor: v("--border"),
secondaryColor: v("--accent-tint"),
tertiaryColor: v("--raised"),
lineColor: v("--muted"),
textColor: v("--text"),
noteBkgColor: v("--warn-tint"),
noteTextColor: v("--text"),
noteBorderColor: v("--border"),
},
});
resolve(m);
};
script.onerror = () => reject(new Error("couldn't load /vendor/mermaid.min.js"));
document.head.append(script);
});
return loading;
}
export function diagram(src: string): HTMLElement {
const el = h("div", { class: "mermaid-diagram" }, h("pre", { class: "mermaid-source" }, src));
void render(el, src);
return el;
}
const latest = new WeakMap<HTMLElement, number>();
export async function redraw(el: HTMLElement, src: string) {
const id = next++;
latest.set(el, id);
try {
const m = await load();
const { svg } = await m.render(`mermaid-${id}`, src);
if (latest.get(el) === id) {
el.innerHTML = svg;
}
} catch (err) {
if (latest.get(el) !== id) {
return;
}
el.replaceChildren(h("pre", { class: "mermaid-source" }, src), h("div", { class: "mermaid-error" }, `diagram not rendered: ${errorText(err)}`));
}
}
async function render(el: HTMLElement, src: string) {
try {
const m = await load();
const { svg } = await m.render(`mermaid-${next++}`, src);
el.innerHTML = svg;
} catch (err) {
el.append(h("div", { class: "mermaid-error" }, `diagram not rendered: ${errorText(err)}`));
}
}