From 87b803790c10dde62cbc5452014c619eced5b36b Mon Sep 17 00:00:00 2001
From: Jacky Zhao <j.zhao2k19@gmail.com>
Date: Tue, 11 Mar 2025 18:45:45 +0000
Subject: [PATCH] fix(mermaid): themechange detector + expand simplification
---
quartz/components/scripts/mermaid.inline.ts | 144 +++++++++++++++-------------
quartz/plugins/transformers/ofm.ts | 60 -----------
quartz/components/styles/mermaid.inline.scss | 48 +-------
3 files changed, 87 insertions(+), 165 deletions(-)
diff --git a/quartz/components/scripts/mermaid.inline.ts b/quartz/components/scripts/mermaid.inline.ts
index 36d384c..19ef24d 100644
--- a/quartz/components/scripts/mermaid.inline.ts
+++ b/quartz/components/scripts/mermaid.inline.ts
@@ -12,7 +12,8 @@
private scale = 1
private readonly MIN_SCALE = 0.5
private readonly MAX_SCALE = 3
- private readonly ZOOM_SENSITIVITY = 0.001
+
+ cleanups: (() => void)[] = []
constructor(
private container: HTMLElement,
@@ -20,19 +21,33 @@
) {
this.setupEventListeners()
this.setupNavigationControls()
+ this.resetTransform()
}
private setupEventListeners() {
// Mouse drag events
- this.container.addEventListener("mousedown", this.onMouseDown.bind(this))
- document.addEventListener("mousemove", this.onMouseMove.bind(this))
- document.addEventListener("mouseup", this.onMouseUp.bind(this))
+ const mouseDownHandler = this.onMouseDown.bind(this)
+ const mouseMoveHandler = this.onMouseMove.bind(this)
+ const mouseUpHandler = this.onMouseUp.bind(this)
+ const resizeHandler = this.resetTransform.bind(this)
- // Wheel zoom events
- this.container.addEventListener("wheel", this.onWheel.bind(this), { passive: false })
+ this.container.addEventListener("mousedown", mouseDownHandler)
+ document.addEventListener("mousemove", mouseMoveHandler)
+ document.addEventListener("mouseup", mouseUpHandler)
+ window.addEventListener("resize", resizeHandler)
- // Reset on window resize
- window.addEventListener("resize", this.resetTransform.bind(this))
+ this.cleanups.push(
+ () => this.container.removeEventListener("mousedown", mouseDownHandler),
+ () => document.removeEventListener("mousemove", mouseMoveHandler),
+ () => document.removeEventListener("mouseup", mouseUpHandler),
+ () => window.removeEventListener("resize", resizeHandler),
+ )
+ }
+
+ cleanup() {
+ for (const cleanup of this.cleanups) {
+ cleanup()
+ }
}
private setupNavigationControls() {
@@ -84,26 +99,6 @@
this.container.style.cursor = "grab"
}
- private onWheel(e: WheelEvent) {
- e.preventDefault()
-
- const delta = -e.deltaY * this.ZOOM_SENSITIVITY
- const newScale = Math.min(Math.max(this.scale + delta, this.MIN_SCALE), this.MAX_SCALE)
-
- // Calculate mouse position relative to content
- const rect = this.content.getBoundingClientRect()
- const mouseX = e.clientX - rect.left
- const mouseY = e.clientY - rect.top
-
- // Adjust pan to zoom around mouse position
- const scaleDiff = newScale - this.scale
- this.currentPan.x -= mouseX * scaleDiff
- this.currentPan.y -= mouseY * scaleDiff
-
- this.scale = newScale
- this.updateTransform()
- }
-
private zoom(delta: number) {
const newScale = Math.min(Math.max(this.scale + delta, this.MIN_SCALE), this.MAX_SCALE)
@@ -126,7 +121,11 @@
private resetTransform() {
this.scale = 1
- this.currentPan = { x: 0, y: 0 }
+ const svg = this.content.querySelector("svg")!
+ this.currentPan = {
+ x: svg.getBoundingClientRect().width / 2,
+ y: svg.getBoundingClientRect().height / 2,
+ }
this.updateTransform()
}
}
@@ -149,38 +148,59 @@
const nodes = center.querySelectorAll("code.mermaid") as NodeListOf<HTMLElement>
if (nodes.length === 0) return
- const computedStyleMap = cssVars.reduce(
- (acc, key) => {
- acc[key] = getComputedStyle(document.documentElement).getPropertyValue(key)
- return acc
- },
- {} as Record<(typeof cssVars)[number], string>,
- )
-
mermaidImport ||= await import(
// @ts-ignore
"https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.4.0/mermaid.esm.min.mjs"
)
const mermaid = mermaidImport.default
- const darkMode = document.documentElement.getAttribute("saved-theme") === "dark"
- mermaid.initialize({
- startOnLoad: false,
- securityLevel: "loose",
- theme: darkMode ? "dark" : "base",
- themeVariables: {
- fontFamily: computedStyleMap["--codeFont"],
- primaryColor: computedStyleMap["--light"],
- primaryTextColor: computedStyleMap["--darkgray"],
- primaryBorderColor: computedStyleMap["--tertiary"],
- lineColor: computedStyleMap["--darkgray"],
- secondaryColor: computedStyleMap["--secondary"],
- tertiaryColor: computedStyleMap["--tertiary"],
- clusterBkg: computedStyleMap["--light"],
- edgeLabelBackground: computedStyleMap["--highlight"],
- },
- })
- await mermaid.run({ nodes })
+ const textMapping: WeakMap<HTMLElement, string> = new WeakMap()
+ for (const node of nodes) {
+ textMapping.set(node, node.innerText)
+ }
+
+ async function renderMermaid() {
+ // de-init any other diagrams
+ for (const node of nodes) {
+ node.removeAttribute("data-processed")
+ const oldText = textMapping.get(node)
+ if (oldText) {
+ node.innerHTML = oldText
+ }
+ }
+
+ const computedStyleMap = cssVars.reduce(
+ (acc, key) => {
+ acc[key] = window.getComputedStyle(document.documentElement).getPropertyValue(key)
+ return acc
+ },
+ {} as Record<(typeof cssVars)[number], string>,
+ )
+
+ const darkMode = document.documentElement.getAttribute("saved-theme") === "dark"
+ mermaid.initialize({
+ startOnLoad: false,
+ securityLevel: "loose",
+ theme: darkMode ? "dark" : "base",
+ themeVariables: {
+ fontFamily: computedStyleMap["--codeFont"],
+ primaryColor: computedStyleMap["--light"],
+ primaryTextColor: computedStyleMap["--darkgray"],
+ primaryBorderColor: computedStyleMap["--tertiary"],
+ lineColor: computedStyleMap["--darkgray"],
+ secondaryColor: computedStyleMap["--secondary"],
+ tertiaryColor: computedStyleMap["--tertiary"],
+ clusterBkg: computedStyleMap["--light"],
+ edgeLabelBackground: computedStyleMap["--highlight"],
+ },
+ })
+
+ await mermaid.run({ nodes })
+ }
+
+ await renderMermaid()
+ document.addEventListener("themechange", renderMermaid)
+ window.addCleanup(() => document.removeEventListener("themechange", renderMermaid))
for (let i = 0; i < nodes.length; i++) {
const codeBlock = nodes[i] as HTMLElement
@@ -203,7 +223,6 @@
if (!popupContainer) return
let panZoom: DiagramPanZoom | null = null
-
function showMermaid() {
const container = popupContainer.querySelector("#mermaid-space") as HTMLElement
const content = popupContainer.querySelector(".mermaid-content") as HTMLElement
@@ -224,24 +243,15 @@
function hideMermaid() {
popupContainer.classList.remove("active")
+ panZoom?.cleanup()
panZoom = null
}
- function handleEscape(e: any) {
- if (e.key === "Escape") {
- hideMermaid()
- }
- }
-
- const closeBtn = popupContainer.querySelector(".close-button") as HTMLButtonElement
-
- closeBtn.addEventListener("click", hideMermaid)
expandBtn.addEventListener("click", showMermaid)
registerEscapeHandler(popupContainer, hideMermaid)
- document.addEventListener("keydown", handleEscape)
window.addCleanup(() => {
- closeBtn.removeEventListener("click", hideMermaid)
+ panZoom?.cleanup()
expandBtn.removeEventListener("click", showMermaid)
})
}
diff --git a/quartz/components/styles/mermaid.inline.scss b/quartz/components/styles/mermaid.inline.scss
index 79a1c84..f25448d 100644
--- a/quartz/components/styles/mermaid.inline.scss
+++ b/quartz/components/styles/mermaid.inline.scss
@@ -53,46 +53,16 @@
}
& > #mermaid-space {
- display: grid;
- width: 90%;
- height: 90vh;
- margin: 5vh auto;
- background: var(--light);
- box-shadow:
- 0 14px 50px rgba(27, 33, 48, 0.12),
- 0 10px 30px rgba(27, 33, 48, 0.16);
+ border: 1px solid var(--lightgray);
+ background-color: var(--light);
+ border-radius: 5px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ height: 80vh;
+ width: 80vw;
overflow: hidden;
- position: relative;
-
- & > .mermaid-header {
- display: flex;
- justify-content: flex-end;
- padding: 1rem;
- border-bottom: 1px solid var(--lightgray);
- background: var(--light);
- z-index: 2;
- max-height: fit-content;
-
- & > .close-button {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- padding: 0;
- background: transparent;
- border: none;
- border-radius: 4px;
- color: var(--darkgray);
- cursor: pointer;
- transition: all 0.2s ease;
-
- &:hover {
- background: var(--lightgray);
- color: var(--dark);
- }
- }
- }
& > .mermaid-content {
padding: 2rem;
diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts
index 12baf2f..1f4873d 100644
--- a/quartz/plugins/transformers/ofm.ts
+++ b/quartz/plugins/transformers/ofm.ts
@@ -675,7 +675,6 @@
properties: {
className: ["expand-button"],
"aria-label": "Expand mermaid diagram",
- "aria-hidden": "true",
"data-view-component": true,
},
children: [
@@ -706,7 +705,7 @@
{
type: "element",
tagName: "div",
- properties: { id: "mermaid-container" },
+ properties: { id: "mermaid-container", role: "dialog" },
children: [
{
type: "element",
@@ -716,63 +715,6 @@
{
type: "element",
tagName: "div",
- properties: { className: ["mermaid-header"] },
- children: [
- {
- type: "element",
- tagName: "button",
- properties: {
- className: ["close-button"],
- "aria-label": "close button",
- },
- children: [
- {
- type: "element",
- tagName: "svg",
- properties: {
- "aria-hidden": "true",
- xmlns: "http://www.w3.org/2000/svg",
- width: 24,
- height: 24,
- viewBox: "0 0 24 24",
- fill: "none",
- stroke: "currentColor",
- "stroke-width": "2",
- "stroke-linecap": "round",
- "stroke-linejoin": "round",
- },
- children: [
- {
- type: "element",
- tagName: "line",
- properties: {
- x1: 18,
- y1: 6,
- x2: 6,
- y2: 18,
- },
- children: [],
- },
- {
- type: "element",
- tagName: "line",
- properties: {
- x1: 6,
- y1: 6,
- x2: 18,
- y2: 18,
- },
- children: [],
- },
- ],
- },
- ],
- },
- ],
- },
- {
- type: "element",
- tagName: "div",
properties: { className: ["mermaid-content"] },
children: [],
},
--
Gitblit v1.10.0