import { DIAGRAM_MISSING, VIEW_FULL_DIAGRAM_TITLE } from "#configuration/strings/diagram.strings"; import { DIAGRAM_RATIO_PROPERTY, VIEWBOX_ATTRIBUTE, VIEWBOX_HEIGHT_INDEX, VIEWBOX_SEPARATOR, VIEWBOX_WIDTH_INDEX, } from "#configuration/constants/diagram.constants"; import { LANGUAGE_ATTRIBUTE, MERMAID_LANGUAGE } from "#configuration/constants/code.constants"; import { createElement, createVectorElement } from "#core/factories/element.factory"; import { DIAGRAM_FILLED } from "#core/ids/diagram.ids"; import type { MermaidBlock } from "#types/block.types"; import type { PanelHandle } from "#types/panel.types"; import { createExpandButton } from "#presentation/components/overlay.component"; import { createPanel } from "#presentation/components/panel.component"; import { isStaticRender } from "#core/predicates/document.predicate"; import { loadDiagram } from "#core/loaders/diagram.loader"; const ratioOf = function ratioOf(vector: Element): number | null { const parts = (vector.getAttribute(VIEWBOX_ATTRIBUTE) ?? "").split(VIEWBOX_SEPARATOR).map(Number); const width = parts[VIEWBOX_WIDTH_INDEX] ?? 0; const height = parts[VIEWBOX_HEIGHT_INDEX] ?? 0; return width > 0 && height > 0 ? width / height : null; }; const fill = function fill(holder: HTMLElement, canvas: HTMLElement, markup: string | null): void { if (markup === null) { holder.replaceChildren(createElement("p", { className: "diagram-missing", text: DIAGRAM_MISSING })); return; } const vector = createVectorElement(markup); const ratio = ratioOf(vector); if (ratio !== null) { canvas.style.setProperty(DIAGRAM_RATIO_PROPERTY, String(ratio)); } holder.replaceChildren(vector); }; const createCanvas = function createCanvas(source: string, className: string): HTMLElement { const holder = createElement("div"); const panel: PanelHandle = createPanel(holder, className, "fit"); if (isStaticRender()) { return panel.element; } void loadDiagram(source).then((markup) => { fill(holder, panel.element, markup); panel.reset(); panel.element.dispatchEvent(new Event(DIAGRAM_FILLED)); }); return panel.element; }; export const renderDiagram = function renderDiagram(block: MermaidBlock): Element { const caption = block.caption ?? VIEW_FULL_DIAGRAM_TITLE; const expand = createExpandButton("diagram-expand", VIEW_FULL_DIAGRAM_TITLE, caption, () => createCanvas(block.text, "diagram-full"), ); const header = createElement("figcaption", { children: [createElement("span", { className: "diagram-caption", text: caption }), expand], className: "diagram-header", }); return createElement("figure", { children: [ header, createCanvas(block.text, "diagram-canvas"), createElement("pre", { attributes: { [LANGUAGE_ATTRIBUTE]: MERMAID_LANGUAGE }, children: [createElement("code", { text: block.text })], className: "diagram-source", }), ], className: "diagram-figure", }); };