1
0
mirror of https://github.com/docker/docs.git synced 2026-10-23 18:09:02 +03:00

ux: improve Mermaid rendering and theme switching (#26304)

## Summary

**Problem:** Mermaid diagrams briefly showed their raw source before the
SVG appeared, stayed on the wrong palette after a light/dark theme
toggle, and could leave Mermaid's error graphic in the page when a
diagram failed to parse.

**Why this change:** Diagrams are used more often in the docs, so the
renderer needs to match the site theme and avoid flashing source text
while SVGs load.

**Solution:** Replace `mermaid.run()` with a controlled
`mermaid.render()` loop that caches each block's source, skips stale
async results, re-renders when the `<html>` dark class changes,
suppresses Mermaid's default error SVG, and hides `pre.mermaid` until an
SVG is present (or a render error sets `data-error` so the source can
still be inspected).

@netlify /get-started/docker-concepts/the-basics/what-is-a-registry/

Preview:
https://deploy-preview-26304--docsdocker.netlify.app/get-started/docker-concepts/the-basics/what-is-a-registry/
Example of pre-fix behavior:
https://deploy-preview-26283--docsdocker.netlify.app/accounts/organization/

Generated by Cursor

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alexa Kristensen
2026-10-08 10:41:05 -05:00
committed by GitHub
parent 8a308ef225
commit e44889768c
2 changed files with 91 additions and 14 deletions

View File

@@ -127,4 +127,14 @@
@apply inline-block rounded-t-sm px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-900;
@apply dark:text-gray-200;
}
}
}
/* Mermaid blocks hold the diagram source until mermaid.js replaces it with
an SVG. Hide the source while that happens, unless rendering failed.
Kept outside @layer components on purpose: the element also carries the
`flex` utility, and Tailwind's utilities layer outranks components
regardless of selector specificity. An unlayered rule wins over both. */
pre.mermaid:not(:has(svg)):not([data-error]) {
display: none;
}

View File

@@ -1,19 +1,86 @@
import mermaid from 'mermaid'
import mermaid from "mermaid";
const isDark = () => document.documentElement.classList.contains('dark')
const SELECTOR = "pre.mermaid";
mermaid.initialize({
startOnLoad: false,
securityLevel: 'strict',
theme: isDark() ? 'dark' : 'default',
})
const isDark = () => document.documentElement.classList.contains("dark");
const render = () => {
mermaid.run({ querySelector: 'pre.mermaid' })
}
// Rendering replaces the element content with an SVG, so the diagram source
// is captured once per element and reused for every redraw.
const sources = new WeakMap();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', render)
const getSource = (el) => {
let source = sources.get(el);
if (source === undefined) {
// Same treatment mermaid.run() gives element content: decode HTML
// entities, but keep <br/> tags because labels use them for line breaks.
const decoder = document.createElement("textarea");
decoder.innerHTML = el.innerHTML;
source = decoder.value.replace(/<br\s*\/?>/gi, "<br/>").trim();
sources.set(el, source);
}
return source;
};
// Incremented on every render request. A render that is no longer the latest
// stops before writing to the page, so a slow render cannot overwrite a
// newer theme.
let generation = 0;
let nextId = 0;
let renderedDark;
const render = async () => {
const current = ++generation;
const dark = isDark();
renderedDark = dark;
const elements = Array.from(document.querySelectorAll(SELECTOR));
if (elements.length === 0) return;
mermaid.initialize({
startOnLoad: false,
securityLevel: "strict",
theme: dark ? "dark" : "default",
// On a syntax error, render() would otherwise leave Mermaid's error
// graphic in a temporary element at the end of <body>. The failing
// element shows its source instead (see the catch below).
suppressErrorRendering: true,
});
for (const el of elements) {
const source = getSource(el);
try {
// mermaid.render() returns the SVG as a string instead of writing it
// into the element, so an existing diagram stays on screen until its
// replacement is ready.
const { svg, bindFunctions } = await mermaid.render(
`mermaid-${nextId++}`,
source,
);
if (current !== generation) return;
el.innerHTML = svg;
bindFunctions?.(el);
delete el.dataset.error;
} catch (error) {
if (current !== generation) return;
// Unhide the element so the source and the console error can be
// matched up.
el.dataset.error = "";
console.error("Mermaid failed to render a diagram", error);
}
}
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", render);
} else {
render()
render();
}
// theme.js and the header theme button both set the light/dark class on
// <html>. Redraw with the matching Mermaid theme whenever it changes.
new MutationObserver(() => {
if (isDark() !== renderedDark) render();
}).observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
});