You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
5.2 KiB
Django/Jinja
189 lines
5.2 KiB
Django/Jinja
{%- macro mermaid_js(
|
|
url="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.7.0/mermaid.esm.min.mjs"
|
|
) -%}
|
|
<script type="module">
|
|
document.addEventListener("DOMContentLoaded", async () => {
|
|
const diagrams = document.querySelectorAll(".jp-Mermaid > pre.mermaid");
|
|
// do not load mermaidjs if not needed
|
|
if (!diagrams.length) {
|
|
return;
|
|
}
|
|
const mermaid = (await import("{{ url }}")).default;
|
|
const parser = new DOMParser();
|
|
|
|
mermaid.initialize({
|
|
maxTextSize: 100000,
|
|
maxEdges: 100000,
|
|
startOnLoad: false,
|
|
fontFamily: window
|
|
.getComputedStyle(document.body)
|
|
.getPropertyValue("--jp-ui-font-family"),
|
|
theme: document.querySelector("body[data-jp-theme-light='true']")
|
|
? "default"
|
|
: "dark",
|
|
});
|
|
|
|
let _nextMermaidId = 0;
|
|
|
|
function makeMermaidImage(svg) {
|
|
const img = document.createElement("img");
|
|
const doc = parser.parseFromString(svg, "image/svg+xml");
|
|
const svgEl = doc.querySelector("svg");
|
|
const { maxWidth } = svgEl?.style || {};
|
|
const firstTitle = doc.querySelector("title");
|
|
const firstDesc = doc.querySelector("desc");
|
|
|
|
img.setAttribute("src", `data:image/svg+xml,${encodeURIComponent(svg)}`);
|
|
if (maxWidth) {
|
|
img.width = parseInt(maxWidth);
|
|
}
|
|
if (firstTitle) {
|
|
img.setAttribute("alt", firstTitle.textContent);
|
|
}
|
|
if (firstDesc) {
|
|
const caption = document.createElement("figcaption");
|
|
caption.className = "sr-only";
|
|
caption.textContent = firstDesc.textContent;
|
|
return [img, caption];
|
|
}
|
|
return [img];
|
|
}
|
|
|
|
async function makeMermaidError(text) {
|
|
let errorMessage = "";
|
|
try {
|
|
await mermaid.parse(text);
|
|
} catch (err) {
|
|
errorMessage = `${err}`;
|
|
}
|
|
|
|
const result = document.createElement("details");
|
|
result.className = 'jp-RenderedMermaid-Details';
|
|
const summary = document.createElement("summary");
|
|
summary.className = 'jp-RenderedMermaid-Summary';
|
|
const pre = document.createElement("pre");
|
|
const code = document.createElement("code");
|
|
code.innerText = text;
|
|
pre.appendChild(code);
|
|
summary.appendChild(pre);
|
|
result.appendChild(summary);
|
|
|
|
const warning = document.createElement("pre");
|
|
warning.innerText = errorMessage;
|
|
result.appendChild(warning);
|
|
return [result];
|
|
}
|
|
|
|
async function renderOneMarmaid(src) {
|
|
const id = `jp-mermaid-${_nextMermaidId++}`;
|
|
const parent = src.parentNode;
|
|
let raw = src.textContent.trim();
|
|
const el = document.createElement("div");
|
|
el.style.visibility = "hidden";
|
|
document.body.appendChild(el);
|
|
let results = null;
|
|
let output = null;
|
|
try {
|
|
let { svg } = await mermaid.render(id, raw, el);
|
|
svg = cleanMermaidSvg(svg);
|
|
results = makeMermaidImage(svg);
|
|
output = document.createElement("figure");
|
|
results.map(output.appendChild, output);
|
|
} catch (err) {
|
|
parent.classList.add("jp-mod-warning");
|
|
results = await makeMermaidError(raw);
|
|
output = results[0];
|
|
} finally {
|
|
el.remove();
|
|
}
|
|
parent.classList.add("jp-RenderedMermaid");
|
|
parent.appendChild(output);
|
|
}
|
|
|
|
|
|
/**
|
|
* Post-process to ensure mermaid diagrams contain only valid SVG and XHTML.
|
|
*/
|
|
function cleanMermaidSvg(svg) {
|
|
return svg.replace(RE_VOID_ELEMENT, replaceVoidElement);
|
|
}
|
|
|
|
|
|
/**
|
|
* A regular expression for all void elements, which may include attributes and
|
|
* a slash.
|
|
*
|
|
* @see https://developer.mozilla.org/en-US/docs/Glossary/Void_element
|
|
*
|
|
* Of these, only `<br>` is generated by Mermaid in place of `\n`,
|
|
* but _any_ "malformed" tag will break the SVG rendering entirely.
|
|
*/
|
|
const RE_VOID_ELEMENT =
|
|
/<\s*(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)\s*([^>]*?)\s*>/gi;
|
|
|
|
/**
|
|
* Ensure a void element is closed with a slash, preserving any attributes.
|
|
*/
|
|
function replaceVoidElement(match, tag, rest) {
|
|
rest = rest.trim();
|
|
if (!rest.endsWith('/')) {
|
|
rest = `${rest} /`;
|
|
}
|
|
return `<${tag} ${rest}>`;
|
|
}
|
|
|
|
void Promise.all([...diagrams].map(renderOneMarmaid));
|
|
});
|
|
</script>
|
|
<style>
|
|
.jp-Mermaid:not(.jp-RenderedMermaid) {
|
|
display: none;
|
|
}
|
|
|
|
.jp-RenderedMermaid {
|
|
overflow: auto;
|
|
display: flex;
|
|
}
|
|
|
|
.jp-RenderedMermaid.jp-mod-warning {
|
|
width: auto;
|
|
padding: 0.5em;
|
|
margin-top: 0.5em;
|
|
border: var(--jp-border-width) solid var(--jp-warn-color2);
|
|
border-radius: var(--jp-border-radius);
|
|
color: var(--jp-ui-font-color1);
|
|
font-size: var(--jp-ui-font-size1);
|
|
white-space: pre-wrap;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
.jp-RenderedMermaid figure {
|
|
margin: 0;
|
|
overflow: auto;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.jp-RenderedMermaid img {
|
|
max-width: 100%;
|
|
}
|
|
|
|
.jp-RenderedMermaid-Details > pre {
|
|
margin-top: 1em;
|
|
}
|
|
|
|
.jp-RenderedMermaid-Summary {
|
|
color: var(--jp-warn-color2);
|
|
}
|
|
|
|
.jp-RenderedMermaid:not(.jp-mod-warning) pre {
|
|
display: none;
|
|
}
|
|
|
|
.jp-RenderedMermaid-Summary > pre {
|
|
display: inline-block;
|
|
white-space: normal;
|
|
}
|
|
</style>
|
|
<!-- End of mermaid configuration -->
|
|
{%- endmacro %}
|