~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
76 lines · 2.1 KB Raw History
// Mermaid diagrams in Markdown, drawn in the browser by Pele
// (https://pele.run). The server sends a ```mermaid block as code, so a
// page without scripts still shows its source; this swaps each block for
// its diagram, loading Pele, and each kind of diagram, only once a page
// has one. A diagram that cannot be drawn shows its source again.

import type { Mounted, MountOptions } from 'pele/lazy';

const BLOCKS = 'pre > code.language-mermaid';

/** Anyone with a repository writes diagrams, so their links are not vouched for. */
const OPTIONS: MountOptions = { linkRel: 'noopener nofollow ugc' };

/**
 * An action drawing the Mermaid blocks in its element, again as the HTML it
 * shows changes. The element is marked `data-drawing` until they are drawn:
 * what the screenshots wait for.
 */
export function diagrams(el: HTMLElement, _html: string) {
	let mounted: Mounted[] = [];
	let run = 0;
	const clear = () => {
		run++;
		mounted.forEach((m) => m.destroy());
		mounted = [];
	};
	async function draw(id: number) {
		const blocks = [...el.querySelectorAll<HTMLElement>(BLOCKS)];
		if (!blocks.length) return;
		let pele: typeof import('pele/lazy');
		try {
			pele = await import('pele/lazy');
		} catch {
			// Not loaded: the sources stay shown.
			return;
		}
		for (const code of blocks) {
			const pre = code.parentElement;
			if (id !== run || !pre?.isConnected) return;
			const box = document.createElement('div');
			box.className = 'diagram';
			pre.replaceWith(box);
			let m: Mounted | undefined;
			try {
				m = await pele.mountAsync(box, code.textContent ?? '', OPTIONS);
			} catch {
				// Its kind of diagram failed to load.
			}
			if (m && m.error === undefined && id === run) {
				mounted.push(m);
				continue;
			}
			// Not drawn, or no longer wanted: the source again.
			m?.destroy();
			box.replaceWith(pre);
		}
	}
	function start() {
		const id = run;
		el.dataset.drawing = '';
		// After the HTML it is given is in place.
		queueMicrotask(() =>
			draw(id).finally(() => {
				if (id === run) delete el.dataset.drawing;
			})
		);
	}
	start();
	return {
		update() {
			clear();
			start();
		},
		destroy: clear
	};
}