diff --git a/quartz/components/scripts/popover.inline.ts b/quartz/components/scripts/popover.inline.ts index 733779369..86cc88582 100644 --- a/quartz/components/scripts/popover.inline.ts +++ b/quartz/components/scripts/popover.inline.ts @@ -1,33 +1,40 @@ import { computePosition, flip, inline, shift } from "@floating-ui/dom" import { normalizeRelativeURLs } from "../../util/path" import { fetchCanonical } from "./util" +import { randomIdNonSecure } from "../../util/random" const p = new DOMParser() + async function mouseEnterHandler( this: HTMLAnchorElement, { clientX, clientY }: { clientX: number; clientY: number }, ) { + clearActivePopover() + const link = this + const id = randomIdNonSecure() if (link.dataset.noPopover === "true") { return } async function setPosition(popoverElement: HTMLElement) { const { x, y } = await computePosition(link, popoverElement, { + strategy: "fixed", middleware: [inline({ x: clientX, y: clientY }), shift(), flip()], }) Object.assign(popoverElement.style, { - left: `${x}px`, - top: `${y}px`, + transform: `translate(${x.toFixed()}px, ${y.toFixed()}px)`, }) } - const hasAlreadyBeenFetched = () => - [...link.children].some((child) => child.classList.contains("popover")) + const prevPopoverElement = document.getElementById(`popover-${id}`) + const hasAlreadyBeenFetched = () => !!document.getElementById(`popover-${id}`) // dont refetch if there's already a popover if (hasAlreadyBeenFetched()) { - return setPosition(link.lastChild as HTMLElement) + setPosition(prevPopoverElement as HTMLElement) + prevPopoverElement?.classList.add("active-popover") + return } const thisUrl = new URL(document.location.href) @@ -82,8 +89,11 @@ async function mouseEnterHandler( const contents = await response.text() const html = p.parseFromString(contents, "text/html") normalizeRelativeURLs(html, targetUrl) - // strip all IDs from elements to prevent duplicates - html.querySelectorAll("[id]").forEach((el) => el.removeAttribute("id")) + // prepend all IDs inside popovers to prevent duplicates + html.querySelectorAll("[id]").forEach((el) => { + const targetID = `popover-internal-${el.id}` + el.id = targetID + }) const elts = [...html.getElementsByClassName("popover-hint")] if (elts.length === 0) return @@ -91,10 +101,13 @@ async function mouseEnterHandler( } setPosition(popoverElement) - link.appendChild(popoverElement) + popoverElement.id = `popover-${id}` + popoverElement.classList.add("active-popover") + document.body.appendChild(popoverElement) if (hash !== "") { - const heading = popoverInner.querySelector(hash) as HTMLElement | null + const targetAnchor = `#popover-internal-${hash.slice(1)}` + const heading = popoverInner.querySelector(targetAnchor) as HTMLElement | null if (heading) { // leave ~12px of buffer when scrolling to a heading popoverInner.scroll({ top: heading.offsetTop - 12, behavior: "instant" }) @@ -102,10 +115,23 @@ async function mouseEnterHandler( } } +function clearActivePopover() { + const allPopoverElements = document.querySelectorAll(".popover") + if (allPopoverElements) { + allPopoverElements.forEach((popoverElement) => + popoverElement.classList.remove("active-popover"), + ) + } +} + document.addEventListener("nav", () => { const links = [...document.getElementsByClassName("internal")] as HTMLAnchorElement[] for (const link of links) { + link.addEventListener("mouseleave", clearActivePopover) link.addEventListener("mouseenter", mouseEnterHandler) - window.addCleanup(() => link.removeEventListener("mouseenter", mouseEnterHandler)) + window.addCleanup(() => { + link.removeEventListener("mouseenter", mouseEnterHandler) + link.removeEventListener("mouseleave", clearActivePopover) + }) } }) diff --git a/quartz/components/styles/backlinks.scss b/quartz/components/styles/backlinks.scss index 71c13f04e..478e118d2 100644 --- a/quartz/components/styles/backlinks.scss +++ b/quartz/components/styles/backlinks.scss @@ -8,10 +8,12 @@ margin: 0; } - & > ul { + & > ul.overflow { list-style: none; padding: 0; margin: 0.5rem 0; + max-height: calc(100% - 2rem); + overscroll-behavior: contain; & > li { & > a { diff --git a/quartz/components/styles/explorer.scss b/quartz/components/styles/explorer.scss index a87411b2d..55c43722f 100644 --- a/quartz/components/styles/explorer.scss +++ b/quartz/components/styles/explorer.scss @@ -118,6 +118,7 @@ button.desktop-explorer { list-style: none; margin: 0; padding: 0; + overscroll-behavior: contain; & li > a { color: var(--dark); diff --git a/quartz/components/styles/popover.scss b/quartz/components/styles/popover.scss index 872045a2c..70e1dd6d2 100644 --- a/quartz/components/styles/popover.scss +++ b/quartz/components/styles/popover.scss @@ -16,9 +16,12 @@ .popover { z-index: 999; - position: absolute; + position: fixed; overflow: visible; padding: 1rem; + left: 0; + top: 0; + will-change: transform; & > .popover-inner { position: relative; @@ -35,6 +38,7 @@ border-radius: 5px; box-shadow: 6px 6px 36px 0 rgba(0, 0, 0, 0.25); overflow: auto; + overscroll-behavior: contain; white-space: normal; user-select: none; cursor: default; @@ -77,7 +81,7 @@ } } -a:hover .popover, +.active-popover, .popover:hover { animation: dropin 0.3s ease; animation-fill-mode: forwards; diff --git a/quartz/components/styles/toc.scss b/quartz/components/styles/toc.scss index d544d0e99..6a7723bdc 100644 --- a/quartz/components/styles/toc.scss +++ b/quartz/components/styles/toc.scss @@ -3,18 +3,11 @@ .toc { display: flex; flex-direction: column; - overflow-y: hidden; - min-height: 1.2rem; - flex: 0 1 auto; + min-height: 1.4rem; + flex: 0 0.5 auto; &:has(button.toc-header.collapsed) { - flex: 0 1 1.2rem; - } -} - -@media all and not ($mobile) { - .toc-header { - display: flex; + flex: 0 1 1.4rem; } } @@ -45,13 +38,15 @@ button.toc-header { } } -ul.toc-content { +ul.toc-content.overflow { list-style: none; position: relative; margin: 0.5rem 0; padding: 0; - + max-height: calc(100% - 2rem); + overscroll-behavior: contain; list-style: none; + & > li > a { color: var(--dark); opacity: 0.35; diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts index 1f4873d22..5c2f4b25c 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts @@ -191,8 +191,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> const [rawFp, rawHeader, rawAlias]: (string | undefined)[] = capture const [fp, anchor] = splitAnchor(`${rawFp ?? ""}${rawHeader ?? ""}`) - const blockRef = Boolean(rawHeader?.match(/^#?\^/)) ? "^" : "" - const displayAnchor = anchor ? `#${blockRef}${anchor.trim().replace(/^#+/, "")}` : "" + const displayAnchor = anchor ? `#${anchor.trim().replace(/^#+/, "")}` : "" const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? "" const embedDisplay = value.startsWith("!") ? "!" : "" diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss index 16383d5b8..60d2d8fa7 100644 --- a/quartz/styles/base.scss +++ b/quartz/styles/base.scss @@ -238,6 +238,7 @@ a { padding: 0; & > * { flex: 1; + max-height: 24rem; } & > .toc { display: none; @@ -577,7 +578,7 @@ ol.overflow { clear: both; & > li.overflow-end { - height: 1rem; + height: 0.5rem; margin: 0; }