Skip to content

Commit

Permalink
[#65532] Mermaid caching
Browse files Browse the repository at this point in the history
  • Loading branch information
Trzcin authored and mgielda committed Sep 11, 2024
1 parent 17b7cca commit 4f31ca9
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 16 deletions.
62 changes: 47 additions & 15 deletions src/hooks/markdownMermaid.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import mermaid from "mermaid";
import { waitForElement } from "../utils";
import { getLineById } from "./markdownSourceMap";
import IMurMurHash from "imurmurhash";

const markdownItMermaid = (md, { preview }) => {
// We want to keep a cache based on line numbers to retrieve the previous version.
// This allows for a flicker-free editing experience.
// key = line number
const lineCache = new Map();
const hashSeed = 42;
// key = hash of diagram source code
const contentCache = new Map();

const markdownItMermaid = (md, { preview, lineMap, parent }) => {
mermaid.initialize({
theme: "neutral",
suppressErrorRendering: true,
Expand All @@ -21,23 +31,45 @@ const markdownItMermaid = (md, { preview }) => {
}

const code = token.content.trim();
const lineNumber = getLineById(lineMap.current, token.attrGet("data-line-id"));
let cached = lineCache.get(lineNumber);
if (!cached) {
cached = contentCache.get(new IMurMurHash(code, hashSeed).result());
}
const id = Math.random().toString().replace(".", "");
token.attrSet("data-mermaid-id", id);

waitForElement(preview.current, `[data-mermaid-id="${id}"]`).then((el) => {
mermaid
.render(`mermaid-${id}`, code)
.then(({ svg }) => {
el.innerHTML = svg;
el.className = "mermaid";
})
.catch((err) => {
el.innerHTML = `<b>Mermaid error:</b>\n${err}`;
el.classList.remove("mermaid");
});
});

return `<pre ${self.renderAttrs(token)}>${code}</pre>`;
if (cached) {
token.attrSet("class", "mermaid");
}

if (!cached || cached.code !== code) {
const container = document.createElement("div");
container.style.position = "fixed";
container.style.visibility = "none";
document.body.appendChild(container);

waitForElement(preview.current, `[data-mermaid-id="${id}"]`).then((el) => {
mermaid
.render(`mermaid-${id}`, code, container)
.then(({ svg }) => {
const saved = { svg, code };
lineCache.set(lineNumber, saved);
contentCache.set(new IMurMurHash(code, hashSeed).result(), saved);
el.innerHTML = svg;
el.className = "mermaid";
})
.catch((err) => {
el.innerHTML = `<b>Mermaid error:</b>\n${err}`;
el.classList.remove("mermaid");
})
.finally(() => {
container.remove();
});
});
}

return `<pre ${self.renderAttrs(token)}>${cached?.svg ?? code}</pre>`;
};
};

Expand Down
8 changes: 8 additions & 0 deletions src/hooks/markdownSourceMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,11 @@ export function findNearestElementForLine(lineNumber, lineMap, preview) {

return [match, num];
}

export function getLineById(lineMap, id) {
for (const [line, value] of lineMap.entries()) {
if (value === id) {
return line;
}
}
}
2 changes: 1 addition & 1 deletion src/hooks/useText.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const useText = ({ initialText, transforms, customRoles, preview, backsla
.use(markdownitDocutils)
.use(markdownReplacer(transforms, parent))
.use(useCustomRoles(customRoles, parent))
.use(markdownMermaid, { preview })
.use(markdownMermaid, { preview, lineMap, parent })
.use(markdownSourceMap);
if (backslashLineBreak) md.use(backslashLineBreakPlugin);
return md;
Expand Down

0 comments on commit 4f31ca9

Please sign in to comment.