diff --git a/quartz.lock.json b/quartz.lock.json index 0e73a7b76..97a3200fb 100644 --- a/quartz.lock.json +++ b/quartz.lock.json @@ -124,8 +124,8 @@ "syntax-highlighting": { "source": "github:quartz-community/syntax-highlighting", "resolved": "https://github.com/quartz-community/syntax-highlighting.git", - "commit": "7255e37de4d17690eba5508944c232c3a85f74d5", - "installedAt": "2026-02-13T22:03:22.542Z" + "commit": "fada634ccd28a29580472555153933dcefe580e0", + "installedAt": "2026-02-13T23:33:13.407Z" }, "obsidian-flavored-markdown": { "source": "github:quartz-community/obsidian-flavored-markdown", diff --git a/quartz/components/Body.tsx b/quartz/components/Body.tsx index 96b627883..d396f4d5d 100644 --- a/quartz/components/Body.tsx +++ b/quartz/components/Body.tsx @@ -1,13 +1,7 @@ -// @ts-ignore -import clipboardScript from "./scripts/clipboard.inline" -import clipboardStyle from "./styles/clipboard.scss" import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" const Body: QuartzComponent = ({ children }: QuartzComponentProps) => { return
{children}
} -Body.afterDOMLoaded = clipboardScript -Body.css = clipboardStyle - export default (() => Body) satisfies QuartzComponentConstructor diff --git a/quartz/components/scripts/callout.inline.ts b/quartz/components/scripts/callout.inline.ts deleted file mode 100644 index 242ce514e..000000000 --- a/quartz/components/scripts/callout.inline.ts +++ /dev/null @@ -1,27 +0,0 @@ -function toggleCallout(this: HTMLElement) { - const outerBlock = this.parentElement! - outerBlock.classList.toggle("is-collapsed") - const content = outerBlock.getElementsByClassName("callout-content")[0] as HTMLElement - if (!content) return - const collapsed = outerBlock.classList.contains("is-collapsed") - content.style.gridTemplateRows = collapsed ? "0fr" : "1fr" -} - -function setupCallout() { - const collapsible = document.getElementsByClassName( - `callout is-collapsible`, - ) as HTMLCollectionOf - for (const div of collapsible) { - const title = div.getElementsByClassName("callout-title")[0] as HTMLElement - const content = div.getElementsByClassName("callout-content")[0] as HTMLElement - if (!title || !content) continue - - title.addEventListener("click", toggleCallout) - window.addCleanup(() => title.removeEventListener("click", toggleCallout)) - - const collapsed = div.classList.contains("is-collapsed") - content.style.gridTemplateRows = collapsed ? "0fr" : "1fr" - } -} - -document.addEventListener("nav", setupCallout) diff --git a/quartz/components/scripts/checkbox.inline.ts b/quartz/components/scripts/checkbox.inline.ts deleted file mode 100644 index 50ab0425a..000000000 --- a/quartz/components/scripts/checkbox.inline.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { getFullSlug } from "../../util/path" - -const checkboxId = (index: number) => `${getFullSlug(window)}-checkbox-${index}` - -document.addEventListener("nav", () => { - const checkboxes = document.querySelectorAll( - "input.checkbox-toggle", - ) as NodeListOf - checkboxes.forEach((el, index) => { - const elId = checkboxId(index) - - const switchState = (e: Event) => { - const newCheckboxState = (e.target as HTMLInputElement)?.checked ? "true" : "false" - localStorage.setItem(elId, newCheckboxState) - } - - el.addEventListener("change", switchState) - window.addCleanup(() => el.removeEventListener("change", switchState)) - if (localStorage.getItem(elId) === "true") { - el.checked = true - } - }) -}) diff --git a/quartz/components/scripts/clipboard.inline.ts b/quartz/components/scripts/clipboard.inline.ts deleted file mode 100644 index e16c11299..000000000 --- a/quartz/components/scripts/clipboard.inline.ts +++ /dev/null @@ -1,37 +0,0 @@ -const svgCopy = - '' -const svgCheck = - '' - -document.addEventListener("nav", () => { - const els = document.getElementsByTagName("pre") - for (let i = 0; i < els.length; i++) { - const codeBlock = els[i].getElementsByTagName("code")[0] - if (codeBlock) { - const source = ( - codeBlock.dataset.clipboard ? JSON.parse(codeBlock.dataset.clipboard) : codeBlock.innerText - ).replace(/\n\n/g, "\n") - const button = document.createElement("button") - button.className = "clipboard-button" - button.type = "button" - button.innerHTML = svgCopy - button.ariaLabel = "Copy source" - function onClick() { - navigator.clipboard.writeText(source).then( - () => { - button.blur() - button.innerHTML = svgCheck - setTimeout(() => { - button.innerHTML = svgCopy - button.style.borderColor = "" - }, 2000) - }, - (error) => console.error(error), - ) - } - button.addEventListener("click", onClick) - window.addCleanup(() => button.removeEventListener("click", onClick)) - els[i].prepend(button) - } - } -}) diff --git a/quartz/components/scripts/mermaid.inline.ts b/quartz/components/scripts/mermaid.inline.ts deleted file mode 100644 index 10399739d..000000000 --- a/quartz/components/scripts/mermaid.inline.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { registerEscapeHandler, removeAllChildren } from "./util" - -interface Position { - x: number - y: number -} - -class DiagramPanZoom { - private isDragging = false - private startPan: Position = { x: 0, y: 0 } - private currentPan: Position = { x: 0, y: 0 } - private scale = 1 - private readonly MIN_SCALE = 0.5 - private readonly MAX_SCALE = 3 - - cleanups: (() => void)[] = [] - - constructor( - private container: HTMLElement, - private content: HTMLElement, - ) { - this.setupEventListeners() - this.setupNavigationControls() - this.resetTransform() - } - - private setupEventListeners() { - // Mouse drag events - const mouseDownHandler = this.onMouseDown.bind(this) - const mouseMoveHandler = this.onMouseMove.bind(this) - const mouseUpHandler = this.onMouseUp.bind(this) - - // Touch drag events - const touchStartHandler = this.onTouchStart.bind(this) - const touchMoveHandler = this.onTouchMove.bind(this) - const touchEndHandler = this.onTouchEnd.bind(this) - - const resizeHandler = this.resetTransform.bind(this) - - this.container.addEventListener("mousedown", mouseDownHandler) - document.addEventListener("mousemove", mouseMoveHandler) - document.addEventListener("mouseup", mouseUpHandler) - - this.container.addEventListener("touchstart", touchStartHandler, { passive: false }) - document.addEventListener("touchmove", touchMoveHandler, { passive: false }) - document.addEventListener("touchend", touchEndHandler) - - window.addEventListener("resize", resizeHandler) - - this.cleanups.push( - () => this.container.removeEventListener("mousedown", mouseDownHandler), - () => document.removeEventListener("mousemove", mouseMoveHandler), - () => document.removeEventListener("mouseup", mouseUpHandler), - () => this.container.removeEventListener("touchstart", touchStartHandler), - () => document.removeEventListener("touchmove", touchMoveHandler), - () => document.removeEventListener("touchend", touchEndHandler), - () => window.removeEventListener("resize", resizeHandler), - ) - } - - cleanup() { - for (const cleanup of this.cleanups) { - cleanup() - } - } - - private setupNavigationControls() { - const controls = document.createElement("div") - controls.className = "mermaid-controls" - - // Zoom controls - const zoomIn = this.createButton("+", () => this.zoom(0.1)) - const zoomOut = this.createButton("-", () => this.zoom(-0.1)) - const resetBtn = this.createButton("Reset", () => this.resetTransform()) - - controls.appendChild(zoomOut) - controls.appendChild(resetBtn) - controls.appendChild(zoomIn) - - this.container.appendChild(controls) - } - - private createButton(text: string, onClick: () => void): HTMLButtonElement { - const button = document.createElement("button") - button.textContent = text - button.className = "mermaid-control-button" - button.addEventListener("click", onClick) - window.addCleanup(() => button.removeEventListener("click", onClick)) - return button - } - - private onMouseDown(e: MouseEvent) { - if (e.button !== 0) return // Only handle left click - this.isDragging = true - this.startPan = { x: e.clientX - this.currentPan.x, y: e.clientY - this.currentPan.y } - this.container.style.cursor = "grabbing" - } - - private onMouseMove(e: MouseEvent) { - if (!this.isDragging) return - e.preventDefault() - - this.currentPan = { - x: e.clientX - this.startPan.x, - y: e.clientY - this.startPan.y, - } - - this.updateTransform() - } - - private onMouseUp() { - this.isDragging = false - this.container.style.cursor = "grab" - } - - private onTouchStart(e: TouchEvent) { - if (e.touches.length !== 1) return - this.isDragging = true - const touch = e.touches[0] - this.startPan = { x: touch.clientX - this.currentPan.x, y: touch.clientY - this.currentPan.y } - } - - private onTouchMove(e: TouchEvent) { - if (!this.isDragging || e.touches.length !== 1) return - e.preventDefault() // Prevent scrolling - - const touch = e.touches[0] - this.currentPan = { - x: touch.clientX - this.startPan.x, - y: touch.clientY - this.startPan.y, - } - - this.updateTransform() - } - - private onTouchEnd() { - this.isDragging = false - } - - private zoom(delta: number) { - const newScale = Math.min(Math.max(this.scale + delta, this.MIN_SCALE), this.MAX_SCALE) - - // Zoom around center - const rect = this.content.getBoundingClientRect() - const centerX = rect.width / 2 - const centerY = rect.height / 2 - - const scaleDiff = newScale - this.scale - this.currentPan.x -= centerX * scaleDiff - this.currentPan.y -= centerY * scaleDiff - - this.scale = newScale - this.updateTransform() - } - - private updateTransform() { - this.content.style.transform = `translate(${this.currentPan.x}px, ${this.currentPan.y}px) scale(${this.scale})` - } - - private resetTransform() { - const svg = this.content.querySelector("svg")! - const rect = svg.getBoundingClientRect() - const width = rect.width / this.scale - const height = rect.height / this.scale - - this.scale = 1 - this.currentPan = { - x: (this.container.clientWidth - width) / 2, - y: (this.container.clientHeight - height) / 2, - } - this.updateTransform() - } -} - -const cssVars = [ - "--secondary", - "--tertiary", - "--gray", - "--light", - "--lightgray", - "--highlight", - "--dark", - "--darkgray", - "--codeFont", -] as const - -let mermaidImport = undefined -document.addEventListener("nav", async () => { - const center = document.querySelector(".center") as HTMLElement - const nodes = center.querySelectorAll("code.mermaid") as NodeListOf - if (nodes.length === 0) return - - mermaidImport ||= await import( - // @ts-ignore - "https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.4.0/mermaid.esm.min.mjs" - ) - const mermaid = mermaidImport.default - - const textMapping: WeakMap = 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 - const pre = codeBlock.parentElement as HTMLPreElement - const clipboardBtn = pre.querySelector(".clipboard-button") as HTMLButtonElement - const expandBtn = pre.querySelector(".expand-button") as HTMLButtonElement - - const clipboardStyle = window.getComputedStyle(clipboardBtn) - const clipboardWidth = - clipboardBtn.offsetWidth + - parseFloat(clipboardStyle.marginLeft || "0") + - parseFloat(clipboardStyle.marginRight || "0") - - // Set expand button position - expandBtn.style.right = `calc(${clipboardWidth}px + 0.3rem)` - pre.prepend(expandBtn) - - // query popup container - const popupContainer = pre.querySelector("#mermaid-container") as HTMLElement - 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 - if (!content) return - removeAllChildren(content) - - // Clone the mermaid content - const mermaidContent = codeBlock.querySelector("svg")!.cloneNode(true) as SVGElement - content.appendChild(mermaidContent) - - // Show container - popupContainer.classList.add("active") - container.style.cursor = "grab" - - // Initialize pan-zoom after showing the popup - panZoom = new DiagramPanZoom(container, content) - } - - function hideMermaid() { - popupContainer.classList.remove("active") - panZoom?.cleanup() - panZoom = null - } - - expandBtn.addEventListener("click", showMermaid) - registerEscapeHandler(popupContainer, hideMermaid) - - window.addCleanup(() => { - panZoom?.cleanup() - expandBtn.removeEventListener("click", showMermaid) - }) - } -}) diff --git a/quartz/components/styles/clipboard.scss b/quartz/components/styles/clipboard.scss deleted file mode 100644 index 196b8945c..000000000 --- a/quartz/components/styles/clipboard.scss +++ /dev/null @@ -1,36 +0,0 @@ -.clipboard-button { - position: absolute; - display: flex; - float: right; - right: 0; - padding: 0.4rem; - margin: 0.3rem; - color: var(--gray); - border-color: var(--dark); - background-color: var(--light); - border: 1px solid; - border-radius: 5px; - opacity: 0; - transition: 0.2s; - - & > svg { - fill: var(--light); - filter: contrast(0.3); - } - - &:hover { - cursor: pointer; - border-color: var(--secondary); - } - - &:focus { - outline: 0; - } -} - -pre { - &:hover > .clipboard-button { - opacity: 1; - transition: 0.2s; - } -} diff --git a/quartz/components/styles/listPage.scss b/quartz/components/styles/listPage.scss deleted file mode 100644 index e86c39dcb..000000000 --- a/quartz/components/styles/listPage.scss +++ /dev/null @@ -1,40 +0,0 @@ -@use "../../styles/variables.scss" as *; - -ul.section-ul { - list-style: none; - margin-top: 2em; - padding-left: 0; -} - -li.section-li { - margin-bottom: 1em; - - & > .section { - display: grid; - grid-template-columns: fit-content(8em) 3fr 1fr; - - @media all and ($mobile) { - & > .tags { - display: none; - } - } - - & > .desc > h3 > a { - background-color: transparent; - } - - & .meta { - margin: 0 1em 0 0; - opacity: 0.6; - } - } -} - -// modifications in popover context -.popover .section { - grid-template-columns: fit-content(8em) 1fr !important; - - & > .tags { - display: none; - } -} diff --git a/quartz/components/styles/mermaid.inline.scss b/quartz/components/styles/mermaid.inline.scss deleted file mode 100644 index 4b11b6dd7..000000000 --- a/quartz/components/styles/mermaid.inline.scss +++ /dev/null @@ -1,132 +0,0 @@ -.expand-button { - position: absolute; - display: flex; - float: right; - padding: 0.4rem; - margin: 0.3rem; - right: 0; // NOTE: right will be set in mermaid.inline.ts - color: var(--gray); - border-color: var(--dark); - background-color: var(--light); - border: 1px solid; - border-radius: 5px; - opacity: 0; - transition: 0.2s; - - & > svg { - fill: var(--light); - filter: contrast(0.3); - } - - &:hover { - cursor: pointer; - border-color: var(--secondary); - } - - &:focus { - outline: 0; - } -} - -pre { - &:hover > .expand-button { - opacity: 1; - transition: 0.2s; - } -} - -#mermaid-container { - position: fixed; - contain: layout; - z-index: 999; - left: 0; - top: 0; - width: 100vw; - height: 100vh; - overflow: hidden; - display: none; - backdrop-filter: blur(4px); - background: rgba(0, 0, 0, 0.5); - - &.active { - display: inline-block; - } - - & > #mermaid-space { - 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; - - & > .mermaid-content { - position: relative; - transform-origin: 0 0; - transition: transform 0.1s ease; - overflow: visible; - min-height: 200px; - min-width: 200px; - - pre { - margin: 0; - border: none; - } - - svg { - max-width: none; - height: auto; - } - } - - & > .mermaid-controls { - position: absolute; - bottom: 20px; - right: 20px; - display: flex; - gap: 8px; - padding: 8px; - background: var(--light); - border: 1px solid var(--lightgray); - border-radius: 6px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - z-index: 2; - - .mermaid-control-button { - display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - padding: 0; - border: 1px solid var(--lightgray); - background: var(--light); - color: var(--dark); - border-radius: 4px; - cursor: pointer; - font-size: 16px; - font-family: var(--bodyFont); - transition: all 0.2s ease; - - &:hover { - background: var(--lightgray); - } - - &:active { - transform: translateY(1px); - } - - // Style the reset button differently - &:nth-child(2) { - width: auto; - padding: 0 12px; - font-size: 14px; - } - } - } - } -}