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

Loading…
Cancel
Save