mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-05-18 14:34:23 +02:00
chore: update latest
Signed-off-by: Aaron Pham <contact@aarnphm.xyz>
This commit is contained in:
parent
3529b10af3
commit
ea6424fed0
@ -4,18 +4,6 @@ const ARTICLE_CONTENT_SELECTOR = ".center"
|
|||||||
const FOOTNOTE_SECTION_SELECTOR = "section[data-footnotes] > ol"
|
const FOOTNOTE_SECTION_SELECTOR = "section[data-footnotes] > ol"
|
||||||
const INDIVIDUAL_FOOTNOTE_SELECTOR = "li[id^='user-content-fn-']"
|
const INDIVIDUAL_FOOTNOTE_SELECTOR = "li[id^='user-content-fn-']"
|
||||||
|
|
||||||
// Computes an offset such that setting `top` on elemToAlign will put it
|
|
||||||
// in vertical alignment with targetAlignment.
|
|
||||||
function computeOffsetForAlignment(elemToAlign: HTMLElement, targetAlignment: HTMLElement) {
|
|
||||||
const offsetParentTop = elemToAlign.offsetParent!.getBoundingClientRect().top
|
|
||||||
return targetAlignment.getBoundingClientRect().top - offsetParentTop
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clamp value between min and max
|
|
||||||
function clamp(value: number, min: number, max: number): number {
|
|
||||||
return Math.max(min, Math.min(value, max))
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInViewport(element: HTMLElement, buffer: number = 100) {
|
function isInViewport(element: HTMLElement, buffer: number = 100) {
|
||||||
const rect = element.getBoundingClientRect()
|
const rect = element.getBoundingClientRect()
|
||||||
return (
|
return (
|
||||||
@ -24,13 +12,17 @@ function isInViewport(element: HTMLElement, buffer: number = 100) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function computeOffsetForAlignment(elemToAlign: HTMLElement, targetAlignment: HTMLElement) {
|
||||||
|
const elemRect = elemToAlign.getBoundingClientRect()
|
||||||
|
const targetRect = targetAlignment.getBoundingClientRect()
|
||||||
|
const parentRect = elemToAlign.parentElement?.getBoundingClientRect() || elemRect
|
||||||
|
return targetRect.top - parentRect.top
|
||||||
|
}
|
||||||
|
|
||||||
// Get bounds for the sidenote positioning
|
// Get bounds for the sidenote positioning
|
||||||
function getSidenoteBounds(
|
function getBounds(parent: HTMLElement, child: HTMLElement): { min: number; max: number } {
|
||||||
sideContainer: HTMLElement,
|
const containerRect = parent.getBoundingClientRect()
|
||||||
sidenote: HTMLElement,
|
const sidenoteRect = child.getBoundingClientRect()
|
||||||
): { min: number; max: number } {
|
|
||||||
const containerRect = sideContainer.getBoundingClientRect()
|
|
||||||
const sidenoteRect = sidenote.getBoundingClientRect()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
min: 0,
|
min: 0,
|
||||||
@ -38,37 +30,40 @@ function getSidenoteBounds(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSidenotes(
|
function updatePosition(ref: HTMLElement, child: HTMLElement, parent: HTMLElement) {
|
||||||
articleContent: HTMLElement,
|
// Calculate ideal position
|
||||||
sideContainer: HTMLElement,
|
let referencePosition = computeOffsetForAlignment(child, ref)
|
||||||
footnoteElements: NodeListOf<HTMLElement>,
|
|
||||||
) {
|
// Get bounds for this sidenote
|
||||||
footnoteElements.forEach((sidenote) => {
|
const bounds = getBounds(parent, child)
|
||||||
|
|
||||||
|
// Clamp the position within bounds
|
||||||
|
referencePosition = Math.max(referencePosition, Math.min(bounds.min, bounds.max))
|
||||||
|
|
||||||
|
// Apply position
|
||||||
|
child.style.top = `${referencePosition}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSidenotes() {
|
||||||
|
const articleContent = document.querySelector(ARTICLE_CONTENT_SELECTOR) as HTMLElement
|
||||||
|
const sideContainer = document.querySelector(".sidenotes") as HTMLElement
|
||||||
|
if (!articleContent || !sideContainer) return
|
||||||
|
|
||||||
|
const sidenotes = sideContainer.querySelectorAll(".sidenote-element") as NodeListOf<HTMLElement>
|
||||||
|
for (const sidenote of sidenotes) {
|
||||||
const sideId = sidenote.id.replace("sidebar-", "")
|
const sideId = sidenote.id.replace("sidebar-", "")
|
||||||
const intextLink = articleContent.querySelector(`a[href="#${sideId}"]`) as HTMLElement
|
const intextLink = articleContent.querySelector(`a[href="#${sideId}"]`) as HTMLElement
|
||||||
if (!intextLink) return
|
if (!intextLink) return
|
||||||
|
|
||||||
// Calculate ideal position
|
|
||||||
let referencePosition = computeOffsetForAlignment(sidenote, intextLink)
|
|
||||||
|
|
||||||
// Get bounds for this sidenote
|
|
||||||
const bounds = getSidenoteBounds(sideContainer, sidenote)
|
|
||||||
|
|
||||||
// Clamp the position within bounds
|
|
||||||
referencePosition = clamp(referencePosition, bounds.min, bounds.max)
|
|
||||||
|
|
||||||
// Apply position
|
|
||||||
sidenote.style.top = `${referencePosition}px`
|
|
||||||
|
|
||||||
// Update visibility state
|
|
||||||
if (isInViewport(intextLink)) {
|
if (isInViewport(intextLink)) {
|
||||||
sidenote.classList.add("in-view")
|
sidenote.classList.add("in-view")
|
||||||
intextLink.classList.add("active")
|
intextLink.classList.add("active")
|
||||||
|
updatePosition(intextLink, sidenote, sideContainer)
|
||||||
} else {
|
} else {
|
||||||
sidenote.classList.remove("in-view")
|
sidenote.classList.remove("in-view")
|
||||||
intextLink.classList.remove("active")
|
intextLink.classList.remove("active")
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function debounce(fn: Function, delay: number) {
|
function debounce(fn: Function, delay: number) {
|
||||||
@ -81,8 +76,11 @@ function debounce(fn: Function, delay: number) {
|
|||||||
|
|
||||||
document.addEventListener("nav", () => {
|
document.addEventListener("nav", () => {
|
||||||
const articleContent = document.querySelector(ARTICLE_CONTENT_SELECTOR) as HTMLElement
|
const articleContent = document.querySelector(ARTICLE_CONTENT_SELECTOR) as HTMLElement
|
||||||
const footnoteSection = document.querySelector(FOOTNOTE_SECTION_SELECTOR)
|
const footnoteSections = Array.from(document.querySelectorAll(FOOTNOTE_SECTION_SELECTOR))
|
||||||
if (!footnoteSection || !articleContent) return
|
if (footnoteSections.length == 0 || !articleContent) return
|
||||||
|
|
||||||
|
const lastIdx = footnoteSections.length - 1
|
||||||
|
const footnoteSection = footnoteSections[lastIdx] as HTMLElement
|
||||||
|
|
||||||
const sideContainer = document.querySelector(".sidenotes") as HTMLElement
|
const sideContainer = document.querySelector(".sidenotes") as HTMLElement
|
||||||
if (!sideContainer) return
|
if (!sideContainer) return
|
||||||
@ -101,7 +99,7 @@ document.addEventListener("nav", () => {
|
|||||||
INDIVIDUAL_FOOTNOTE_SELECTOR,
|
INDIVIDUAL_FOOTNOTE_SELECTOR,
|
||||||
) as NodeListOf<HTMLLIElement>
|
) as NodeListOf<HTMLLIElement>
|
||||||
|
|
||||||
footnotes.forEach((footnote) => {
|
for (const footnote of footnotes) {
|
||||||
const footnoteId = footnote.id
|
const footnoteId = footnote.id
|
||||||
const intextLink = articleContent.querySelector(`a[href="#${footnoteId}"]`) as HTMLElement
|
const intextLink = articleContent.querySelector(`a[href="#${footnoteId}"]`) as HTMLElement
|
||||||
if (!intextLink) return
|
if (!intextLink) return
|
||||||
@ -109,28 +107,33 @@ document.addEventListener("nav", () => {
|
|||||||
const sidenote = document.createElement("li")
|
const sidenote = document.createElement("li")
|
||||||
sidenote.classList.add("sidenote-element")
|
sidenote.classList.add("sidenote-element")
|
||||||
sidenote.style.position = "absolute"
|
sidenote.style.position = "absolute"
|
||||||
|
const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize)
|
||||||
|
sidenote.style.maxWidth = `${sideContainer.offsetWidth - rootFontSize}px`
|
||||||
sidenote.id = `sidebar-${footnoteId}`
|
sidenote.id = `sidebar-${footnoteId}`
|
||||||
const cloned = footnote.cloneNode(true) as HTMLElement
|
const cloned = footnote.cloneNode(true) as HTMLElement
|
||||||
|
const backref = cloned.querySelector("a[data-footnote-backref]")
|
||||||
|
backref?.remove()
|
||||||
sidenote.append(...cloned.children)
|
sidenote.append(...cloned.children)
|
||||||
|
// create inner child container
|
||||||
|
let innerContainer = sidenote.querySelector(".sidenote-inner")
|
||||||
|
if (!innerContainer) {
|
||||||
|
innerContainer = document.createElement("div") as HTMLDivElement
|
||||||
|
innerContainer.className = "sidenote-inner"
|
||||||
|
while (sidenote.firstChild) {
|
||||||
|
innerContainer.appendChild(sidenote.firstChild)
|
||||||
|
}
|
||||||
|
sidenote.appendChild(innerContainer)
|
||||||
|
}
|
||||||
|
|
||||||
ol.appendChild(sidenote)
|
ol.appendChild(sidenote)
|
||||||
})
|
}
|
||||||
|
|
||||||
// Get all sidenotes for updates
|
updateSidenotes()
|
||||||
const sidenotes = sideContainer.querySelectorAll(".sidenote-element") as NodeListOf<HTMLElement>
|
|
||||||
|
|
||||||
// Initial position update
|
|
||||||
updateSidenotes(articleContent, sideContainer, sidenotes)
|
|
||||||
|
|
||||||
// Update on scroll with debouncing
|
// Update on scroll with debouncing
|
||||||
const debouncedUpdate = debounce(
|
const debouncedUpdate = debounce(updateSidenotes, 2)
|
||||||
() => updateSidenotes(articleContent, sideContainer, sidenotes),
|
|
||||||
16, // ~60fps
|
|
||||||
)
|
|
||||||
|
|
||||||
// Add scroll listener
|
|
||||||
document.addEventListener("scroll", debouncedUpdate, { passive: true })
|
document.addEventListener("scroll", debouncedUpdate, { passive: true })
|
||||||
|
|
||||||
// Add resize listener
|
|
||||||
window.addEventListener("resize", debouncedUpdate, { passive: true })
|
window.addEventListener("resize", debouncedUpdate, { passive: true })
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
|
@ -4,12 +4,38 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
& .sidenote-element {
|
& .sidenote-element {
|
||||||
transition: opacity 0.2s ease;
|
position: absolute;
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
display: block;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
border: 1px solid var(--gray);
|
||||||
|
counter-increment: sidenote-counter;
|
||||||
|
background-color: var(--light);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: counter(sidenote-counter);
|
||||||
|
background-color: var(--light);
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
position: absolute;
|
||||||
|
top: -12px;
|
||||||
|
left: 12px;
|
||||||
|
padding: 1px 8px;
|
||||||
|
border: 1px solid var(--tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
&.in-view {
|
&.in-view {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& .sidenote-inner {
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0.2rem 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user