fix(mermaid): themechange detector + expand simplification
This commit is contained in:
parent
e59181c3aa
commit
87b803790c
3 changed files with 87 additions and 165 deletions
|
@ -12,7 +12,8 @@ class DiagramPanZoom {
|
||||||
private scale = 1
|
private scale = 1
|
||||||
private readonly MIN_SCALE = 0.5
|
private readonly MIN_SCALE = 0.5
|
||||||
private readonly MAX_SCALE = 3
|
private readonly MAX_SCALE = 3
|
||||||
private readonly ZOOM_SENSITIVITY = 0.001
|
|
||||||
|
cleanups: (() => void)[] = []
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private container: HTMLElement,
|
private container: HTMLElement,
|
||||||
|
@ -20,19 +21,33 @@ class DiagramPanZoom {
|
||||||
) {
|
) {
|
||||||
this.setupEventListeners()
|
this.setupEventListeners()
|
||||||
this.setupNavigationControls()
|
this.setupNavigationControls()
|
||||||
|
this.resetTransform()
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupEventListeners() {
|
private setupEventListeners() {
|
||||||
// Mouse drag events
|
// Mouse drag events
|
||||||
this.container.addEventListener("mousedown", this.onMouseDown.bind(this))
|
const mouseDownHandler = this.onMouseDown.bind(this)
|
||||||
document.addEventListener("mousemove", this.onMouseMove.bind(this))
|
const mouseMoveHandler = this.onMouseMove.bind(this)
|
||||||
document.addEventListener("mouseup", this.onMouseUp.bind(this))
|
const mouseUpHandler = this.onMouseUp.bind(this)
|
||||||
|
const resizeHandler = this.resetTransform.bind(this)
|
||||||
|
|
||||||
// Wheel zoom events
|
this.container.addEventListener("mousedown", mouseDownHandler)
|
||||||
this.container.addEventListener("wheel", this.onWheel.bind(this), { passive: false })
|
document.addEventListener("mousemove", mouseMoveHandler)
|
||||||
|
document.addEventListener("mouseup", mouseUpHandler)
|
||||||
|
window.addEventListener("resize", resizeHandler)
|
||||||
|
|
||||||
// Reset on window resize
|
this.cleanups.push(
|
||||||
window.addEventListener("resize", this.resetTransform.bind(this))
|
() => 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() {
|
private setupNavigationControls() {
|
||||||
|
@ -84,26 +99,6 @@ class DiagramPanZoom {
|
||||||
this.container.style.cursor = "grab"
|
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) {
|
private zoom(delta: number) {
|
||||||
const newScale = Math.min(Math.max(this.scale + delta, this.MIN_SCALE), this.MAX_SCALE)
|
const newScale = Math.min(Math.max(this.scale + delta, this.MIN_SCALE), this.MAX_SCALE)
|
||||||
|
|
||||||
|
@ -126,7 +121,11 @@ class DiagramPanZoom {
|
||||||
|
|
||||||
private resetTransform() {
|
private resetTransform() {
|
||||||
this.scale = 1
|
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()
|
this.updateTransform()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,38 +148,59 @@ document.addEventListener("nav", async () => {
|
||||||
const nodes = center.querySelectorAll("code.mermaid") as NodeListOf<HTMLElement>
|
const nodes = center.querySelectorAll("code.mermaid") as NodeListOf<HTMLElement>
|
||||||
if (nodes.length === 0) return
|
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(
|
mermaidImport ||= await import(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
"https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.4.0/mermaid.esm.min.mjs"
|
"https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.4.0/mermaid.esm.min.mjs"
|
||||||
)
|
)
|
||||||
const mermaid = mermaidImport.default
|
const mermaid = mermaidImport.default
|
||||||
|
|
||||||
const darkMode = document.documentElement.getAttribute("saved-theme") === "dark"
|
const textMapping: WeakMap<HTMLElement, string> = new WeakMap()
|
||||||
mermaid.initialize({
|
for (const node of nodes) {
|
||||||
startOnLoad: false,
|
textMapping.set(node, node.innerText)
|
||||||
securityLevel: "loose",
|
}
|
||||||
theme: darkMode ? "dark" : "base",
|
|
||||||
themeVariables: {
|
async function renderMermaid() {
|
||||||
fontFamily: computedStyleMap["--codeFont"],
|
// de-init any other diagrams
|
||||||
primaryColor: computedStyleMap["--light"],
|
for (const node of nodes) {
|
||||||
primaryTextColor: computedStyleMap["--darkgray"],
|
node.removeAttribute("data-processed")
|
||||||
primaryBorderColor: computedStyleMap["--tertiary"],
|
const oldText = textMapping.get(node)
|
||||||
lineColor: computedStyleMap["--darkgray"],
|
if (oldText) {
|
||||||
secondaryColor: computedStyleMap["--secondary"],
|
node.innerHTML = oldText
|
||||||
tertiaryColor: computedStyleMap["--tertiary"],
|
}
|
||||||
clusterBkg: computedStyleMap["--light"],
|
}
|
||||||
edgeLabelBackground: computedStyleMap["--highlight"],
|
|
||||||
},
|
const computedStyleMap = cssVars.reduce(
|
||||||
})
|
(acc, key) => {
|
||||||
await mermaid.run({ nodes })
|
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++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
const codeBlock = nodes[i] as HTMLElement
|
const codeBlock = nodes[i] as HTMLElement
|
||||||
|
@ -203,7 +223,6 @@ document.addEventListener("nav", async () => {
|
||||||
if (!popupContainer) return
|
if (!popupContainer) return
|
||||||
|
|
||||||
let panZoom: DiagramPanZoom | null = null
|
let panZoom: DiagramPanZoom | null = null
|
||||||
|
|
||||||
function showMermaid() {
|
function showMermaid() {
|
||||||
const container = popupContainer.querySelector("#mermaid-space") as HTMLElement
|
const container = popupContainer.querySelector("#mermaid-space") as HTMLElement
|
||||||
const content = popupContainer.querySelector(".mermaid-content") as HTMLElement
|
const content = popupContainer.querySelector(".mermaid-content") as HTMLElement
|
||||||
|
@ -224,24 +243,15 @@ document.addEventListener("nav", async () => {
|
||||||
|
|
||||||
function hideMermaid() {
|
function hideMermaid() {
|
||||||
popupContainer.classList.remove("active")
|
popupContainer.classList.remove("active")
|
||||||
|
panZoom?.cleanup()
|
||||||
panZoom = null
|
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)
|
expandBtn.addEventListener("click", showMermaid)
|
||||||
registerEscapeHandler(popupContainer, hideMermaid)
|
registerEscapeHandler(popupContainer, hideMermaid)
|
||||||
document.addEventListener("keydown", handleEscape)
|
|
||||||
|
|
||||||
window.addCleanup(() => {
|
window.addCleanup(() => {
|
||||||
closeBtn.removeEventListener("click", hideMermaid)
|
panZoom?.cleanup()
|
||||||
expandBtn.removeEventListener("click", showMermaid)
|
expandBtn.removeEventListener("click", showMermaid)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,46 +53,16 @@ pre {
|
||||||
}
|
}
|
||||||
|
|
||||||
& > #mermaid-space {
|
& > #mermaid-space {
|
||||||
display: grid;
|
border: 1px solid var(--lightgray);
|
||||||
width: 90%;
|
background-color: var(--light);
|
||||||
height: 90vh;
|
border-radius: 5px;
|
||||||
margin: 5vh auto;
|
position: fixed;
|
||||||
background: var(--light);
|
top: 50%;
|
||||||
box-shadow:
|
left: 50%;
|
||||||
0 14px 50px rgba(27, 33, 48, 0.12),
|
transform: translate(-50%, -50%);
|
||||||
0 10px 30px rgba(27, 33, 48, 0.16);
|
height: 80vh;
|
||||||
|
width: 80vw;
|
||||||
overflow: hidden;
|
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 {
|
& > .mermaid-content {
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
|
|
|
@ -675,7 +675,6 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>>
|
||||||
properties: {
|
properties: {
|
||||||
className: ["expand-button"],
|
className: ["expand-button"],
|
||||||
"aria-label": "Expand mermaid diagram",
|
"aria-label": "Expand mermaid diagram",
|
||||||
"aria-hidden": "true",
|
|
||||||
"data-view-component": true,
|
"data-view-component": true,
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
|
@ -706,70 +705,13 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>>
|
||||||
{
|
{
|
||||||
type: "element",
|
type: "element",
|
||||||
tagName: "div",
|
tagName: "div",
|
||||||
properties: { id: "mermaid-container" },
|
properties: { id: "mermaid-container", role: "dialog" },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
type: "element",
|
type: "element",
|
||||||
tagName: "div",
|
tagName: "div",
|
||||||
properties: { id: "mermaid-space" },
|
properties: { id: "mermaid-space" },
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
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",
|
type: "element",
|
||||||
tagName: "div",
|
tagName: "div",
|
||||||
|
|
Loading…
Add table
Reference in a new issue