fix: fix sidebar active status not working as expected (#140) (#149)

pull/150/head
Kia King Ishii 4 years ago committed by GitHub
parent a28c7376be
commit 0b181e7582
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,19 +3,42 @@ import { onMounted, onUnmounted, onUpdated } from 'vue'
export function useActiveSidebarLinks() { export function useActiveSidebarLinks() {
let rootActiveLink: HTMLAnchorElement | null = null let rootActiveLink: HTMLAnchorElement | null = null
let activeLink: HTMLAnchorElement | null = null let activeLink: HTMLAnchorElement | null = null
const decode = decodeURIComponent
const deactiveLink = (link: HTMLAnchorElement | null) => const onScroll = throttleAndDebounce(setActiveLink, 300)
link && link.classList.remove('active')
function setActiveLink(): void {
const sidebarLinks = getSidebarLinks()
const anchors = getAnchors(sidebarLinks)
const activateLink = (hash: string) => { for (let i = 0; i < anchors.length; i++) {
const anchor = anchors[i]
const nextAnchor = anchors[i + 1]
const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor)
if (isActive) {
history.replaceState(null, document.title, hash ? hash : ' ')
activateLink(hash)
return
}
}
}
function activateLink(hash: string | null): void {
deactiveLink(activeLink) deactiveLink(activeLink)
deactiveLink(rootActiveLink) deactiveLink(rootActiveLink)
activeLink = document.querySelector(`.sidebar a[href="${hash}"]`) activeLink = document.querySelector(`.sidebar a[href="${hash}"]`)
if (activeLink) {
if (!activeLink) {
return
}
activeLink.classList.add('active') activeLink.classList.add('active')
// also add active class to parent h2 anchors // also add active class to parent h2 anchors
const rootLi = activeLink.closest('.sidebar > ul > li') const rootLi = activeLink.closest('.sidebar-links > ul > li')
if (rootLi && rootLi !== activeLink.parentElement) { if (rootLi && rootLi !== activeLink.parentElement) {
rootActiveLink = rootLi.querySelector('a') rootActiveLink = rootLi.querySelector('a')
rootActiveLink && rootActiveLink.classList.add('active') rootActiveLink && rootActiveLink.classList.add('active')
@ -23,66 +46,81 @@ export function useActiveSidebarLinks() {
rootActiveLink = null rootActiveLink = null
} }
} }
function deactiveLink(link: HTMLAnchorElement | null): void {
link && link.classList.remove('active')
} }
const setActiveLink = () => { onMounted(() => {
const sidebarLinks = [].slice.call( setActiveLink()
document.querySelectorAll('.sidebar a') window.addEventListener('scroll', onScroll)
) as HTMLAnchorElement[] })
onUpdated(() => {
// sidebar update means a route change
activateLink(decodeURIComponent(location.hash))
})
const anchors = [].slice onUnmounted(() => {
window.removeEventListener('scroll', onScroll)
})
}
function getSidebarLinks(): HTMLAnchorElement[] {
return [].slice.call(
document.querySelectorAll('.sidebar a.sidebar-link-item')
)
}
function getAnchors(sidebarLinks: HTMLAnchorElement[]): HTMLAnchorElement[] {
return [].slice
.call(document.querySelectorAll('.header-anchor')) .call(document.querySelectorAll('.header-anchor'))
.filter((anchor: HTMLAnchorElement) => .filter((anchor: HTMLAnchorElement) =>
sidebarLinks.some((sidebarLink) => sidebarLink.hash === anchor.hash) sidebarLinks.some((sidebarLink) => sidebarLink.hash === anchor.hash)
) as HTMLAnchorElement[] ) as HTMLAnchorElement[]
}
const pageOffset = (document.querySelector('.navbar') as HTMLElement) function getPageOffset(): number {
.offsetHeight return (document.querySelector('.navbar') as HTMLElement).offsetHeight
const scrollTop = window.scrollY }
const getAnchorTop = (anchor: HTMLAnchorElement): number => function getAnchorTop(anchor: HTMLAnchorElement): number {
anchor.parentElement!.offsetTop - pageOffset - 15 const pageOffset = getPageOffset()
for (let i = 0; i < anchors.length; i++) { return anchor.parentElement!.offsetTop - pageOffset - 15
const anchor = anchors[i] }
const nextAnchor = anchors[i + 1]
const isActive =
(i === 0 && scrollTop === 0) ||
(scrollTop >= getAnchorTop(anchor) &&
(!nextAnchor || scrollTop < getAnchorTop(nextAnchor)))
// TODO: fix case when at page bottom function isAnchorActive(
index: number,
anchor: HTMLAnchorElement,
nextAnchor: HTMLAnchorElement
): [boolean, string | null] {
const scrollTop = window.scrollY
if (isActive) { if (index === 0 && scrollTop === 0) {
const targetHash = decode(anchor.hash) return [true, null]
history.replaceState(null, document.title, targetHash)
activateLink(targetHash)
return
}
}
} }
const onScroll = throttleAndDebounce(setActiveLink, 300) if (scrollTop < getAnchorTop(anchor)) {
onMounted(() => { return [false, null]
setActiveLink() }
window.addEventListener('scroll', onScroll)
})
onUpdated(() => { if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) {
// sidebar update means a route change return [true, decodeURIComponent(anchor.hash)]
activateLink(decode(location.hash)) }
})
onUnmounted(() => { return [false, null]
window.removeEventListener('scroll', onScroll)
})
} }
function throttleAndDebounce(fn: () => void, delay: number): () => void { function throttleAndDebounce(fn: () => void, delay: number): () => void {
let timeout: NodeJS.Timeout let timeout: NodeJS.Timeout
let called = false let called = false
return () => { return () => {
if (timeout) clearTimeout(timeout) if (timeout) {
clearTimeout(timeout)
}
if (!called) { if (!called) {
fn() fn()
called = true called = true

Loading…
Cancel
Save