Compare commits

...

3 Commits

Author SHA1 Message Date
Jacky Zhao
ee8c1dc968 chore(css): style tweaks for overflow
Some checks failed
Build and Test / build-and-test (ubuntu-latest) (push) Has been skipped
Build and Test / publish-tag (push) Has been skipped
Build and Test / build-and-test (macos-latest) (push) Has been cancelled
Build and Test / build-and-test (windows-latest) (push) Has been cancelled
2025-04-05 11:38:50 -07:00
1 gal Rosemary
bb24cd13c7
fix(css): styles issues with popover, overflow, and scroll overflow (#1907)
* fix(style): fix toc overflow & scrolling overflow

* fix(style): fix explorer scrolling overflow

* fix(style): fix backlinks overflow & scrolling overflow

* fix(style): resolve popover overflow issue causing incomplete display

* chore: rename function to enhance readability

* fix(popover): make the backlink's id unique & use translate() instead of translateY()
2025-04-05 10:39:28 -07:00
Emile Bangma
d61fb266c7
fix(popover): automatically position anchored links properly (#1897)
* fix(popover): automatically position heading links at heading

* Impement linking of blockreferences

* Popover fixes

* id mapping

* Remove excess regexes

* Updated blockref

* Remove linker element

* Restore the docs to their former glory

* Move the hash out of the loop

* Redundant

* Redundant

* Restore docs

* Remove log

* Let it const
2025-04-05 10:31:17 -07:00
7 changed files with 56 additions and 28 deletions

View File

@ -1,33 +1,40 @@
import { computePosition, flip, inline, shift } from "@floating-ui/dom" import { computePosition, flip, inline, shift } from "@floating-ui/dom"
import { normalizeRelativeURLs } from "../../util/path" import { normalizeRelativeURLs } from "../../util/path"
import { fetchCanonical } from "./util" import { fetchCanonical } from "./util"
import { randomIdNonSecure } from "../../util/random"
const p = new DOMParser() const p = new DOMParser()
async function mouseEnterHandler( async function mouseEnterHandler(
this: HTMLAnchorElement, this: HTMLAnchorElement,
{ clientX, clientY }: { clientX: number; clientY: number }, { clientX, clientY }: { clientX: number; clientY: number },
) { ) {
clearActivePopover()
const link = this const link = this
const id = randomIdNonSecure()
if (link.dataset.noPopover === "true") { if (link.dataset.noPopover === "true") {
return return
} }
async function setPosition(popoverElement: HTMLElement) { async function setPosition(popoverElement: HTMLElement) {
const { x, y } = await computePosition(link, popoverElement, { const { x, y } = await computePosition(link, popoverElement, {
strategy: "fixed",
middleware: [inline({ x: clientX, y: clientY }), shift(), flip()], middleware: [inline({ x: clientX, y: clientY }), shift(), flip()],
}) })
Object.assign(popoverElement.style, { Object.assign(popoverElement.style, {
left: `${x}px`, transform: `translate(${x}px, ${y}px)`,
top: `${y}px`,
}) })
} }
const hasAlreadyBeenFetched = () => const prevPopoverElement = document.getElementById(`popover-${id}`)
[...link.children].some((child) => child.classList.contains("popover")) const hasAlreadyBeenFetched = () => !!document.getElementById(`popover-${id}`)
// dont refetch if there's already a popover // dont refetch if there's already a popover
if (hasAlreadyBeenFetched()) { 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) const thisUrl = new URL(document.location.href)
@ -82,8 +89,11 @@ async function mouseEnterHandler(
const contents = await response.text() const contents = await response.text()
const html = p.parseFromString(contents, "text/html") const html = p.parseFromString(contents, "text/html")
normalizeRelativeURLs(html, targetUrl) normalizeRelativeURLs(html, targetUrl)
// strip all IDs from elements to prevent duplicates // prepend all IDs inside popovers to prevent duplicates
html.querySelectorAll("[id]").forEach((el) => el.removeAttribute("id")) html.querySelectorAll("[id]").forEach((el) => {
const targetID = `popover-internal-${el.id}`
el.id = targetID
})
const elts = [...html.getElementsByClassName("popover-hint")] const elts = [...html.getElementsByClassName("popover-hint")]
if (elts.length === 0) return if (elts.length === 0) return
@ -91,10 +101,13 @@ async function mouseEnterHandler(
} }
setPosition(popoverElement) setPosition(popoverElement)
link.appendChild(popoverElement) popoverElement.id = `popover-${id}`
popoverElement.classList.add("active-popover")
document.body.appendChild(popoverElement)
if (hash !== "") { 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) { if (heading) {
// leave ~12px of buffer when scrolling to a heading // leave ~12px of buffer when scrolling to a heading
popoverInner.scroll({ top: heading.offsetTop - 12, behavior: "instant" }) 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", () => { document.addEventListener("nav", () => {
const links = [...document.getElementsByClassName("internal")] as HTMLAnchorElement[] const links = [...document.getElementsByClassName("internal")] as HTMLAnchorElement[]
for (const link of links) { for (const link of links) {
link.addEventListener("mouseleave", clearActivePopover)
link.addEventListener("mouseenter", mouseEnterHandler) link.addEventListener("mouseenter", mouseEnterHandler)
window.addCleanup(() => link.removeEventListener("mouseenter", mouseEnterHandler)) window.addCleanup(() => {
link.removeEventListener("mouseenter", mouseEnterHandler)
link.removeEventListener("mouseleave", clearActivePopover)
})
} }
}) })

View File

@ -8,10 +8,12 @@
margin: 0; margin: 0;
} }
& > ul { & > ul.overflow {
list-style: none; list-style: none;
padding: 0; padding: 0;
margin: 0.5rem 0; margin: 0.5rem 0;
max-height: calc(100% - 2rem);
overscroll-behavior: contain;
& > li { & > li {
& > a { & > a {

View File

@ -118,6 +118,7 @@ button.desktop-explorer {
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
overscroll-behavior: contain;
& li > a { & li > a {
color: var(--dark); color: var(--dark);

View File

@ -16,9 +16,12 @@
.popover { .popover {
z-index: 999; z-index: 999;
position: absolute; position: fixed;
overflow: visible; overflow: visible;
padding: 1rem; padding: 1rem;
left: 0;
top: 0;
will-change: transform;
& > .popover-inner { & > .popover-inner {
position: relative; position: relative;
@ -35,6 +38,7 @@
border-radius: 5px; border-radius: 5px;
box-shadow: 6px 6px 36px 0 rgba(0, 0, 0, 0.25); box-shadow: 6px 6px 36px 0 rgba(0, 0, 0, 0.25);
overflow: auto; overflow: auto;
overscroll-behavior: contain;
white-space: normal; white-space: normal;
user-select: none; user-select: none;
cursor: default; cursor: default;
@ -77,7 +81,7 @@
} }
} }
a:hover .popover, .active-popover,
.popover:hover { .popover:hover {
animation: dropin 0.3s ease; animation: dropin 0.3s ease;
animation-fill-mode: forwards; animation-fill-mode: forwards;

View File

@ -3,18 +3,11 @@
.toc { .toc {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow-y: hidden; overflow-y: hidden;
min-height: 1.2rem; min-height: 1.4rem;
flex: 0 1 auto; flex: 0 0.5 auto;
&:has(button.toc-header.collapsed) { &:has(button.toc-header.collapsed) {
flex: 0 1 1.2rem; flex: 0 1 1.4rem;
}
}
@media all and not ($mobile) {
.toc-header {
display: flex;
} }
} }
@ -45,13 +38,15 @@ button.toc-header {
} }
} }
ul.toc-content { ul.toc-content.overflow {
list-style: none; list-style: none;
position: relative; position: relative;
margin: 0.5rem 0; margin: 0.5rem 0;
padding: 0; padding: 0;
max-height: calc(100% - 2rem);
overscroll-behavior: contain;
list-style: none; list-style: none;
& > li > a { & > li > a {
color: var(--dark); color: var(--dark);
opacity: 0.35; opacity: 0.35;

View File

@ -191,8 +191,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>>
const [rawFp, rawHeader, rawAlias]: (string | undefined)[] = capture const [rawFp, rawHeader, rawAlias]: (string | undefined)[] = capture
const [fp, anchor] = splitAnchor(`${rawFp ?? ""}${rawHeader ?? ""}`) const [fp, anchor] = splitAnchor(`${rawFp ?? ""}${rawHeader ?? ""}`)
const blockRef = Boolean(rawHeader?.match(/^#?\^/)) ? "^" : "" const displayAnchor = anchor ? `#${anchor.trim().replace(/^#+/, "")}` : ""
const displayAnchor = anchor ? `#${blockRef}${anchor.trim().replace(/^#+/, "")}` : ""
const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? "" const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? ""
const embedDisplay = value.startsWith("!") ? "!" : "" const embedDisplay = value.startsWith("!") ? "!" : ""

View File

@ -238,6 +238,7 @@ a {
padding: 0; padding: 0;
& > * { & > * {
flex: 1; flex: 1;
max-height: 24rem;
} }
& > .toc { & > .toc {
display: none; display: none;
@ -577,7 +578,7 @@ ol.overflow {
clear: both; clear: both;
& > li.overflow-end { & > li.overflow-end {
height: 1rem; height: 0.5rem;
margin: 0; margin: 0;
} }