import { onMounted , onUnmounted , onUpdated } from 'vue' ; export function useActiveSidebarLinks ( ) { let rootActiveLink = null ; let activeLink = null ; const onScroll = throttleAndDebounce ( setActiveLink , 300 ) ; function setActiveLink ( ) { const sidebarLinks = getSidebarLinks ( ) ; const anchors = getAnchors ( sidebarLinks ) ; 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 ) { deactiveLink ( activeLink ) ; deactiveLink ( rootActiveLink ) ; activeLink = document . querySelector ( `.sidebar a[href="${ hash }"]` ) ; if ( ! activeLink ) { return ; } activeLink . classList . add ( 'active' ) ; const rootLi = activeLink . closest ( '.sidebar-links > ul > li' ) ; if ( rootLi && rootLi !== activeLink . parentElement ) { rootActiveLink = rootLi . querySelector ( 'a' ) ; rootActiveLink && rootActiveLink . classList . add ( 'active' ) ; } else { rootActiveLink = null ; } } function deactiveLink ( link ) { link && link . classList . remove ( 'active' ) ; } onMounted ( ( ) => { setActiveLink ( ) ; window . addEventListener ( 'scroll' , onScroll ) ; } ) ; onUpdated ( ( ) => { activateLink ( decodeURIComponent ( location . hash ) ) ; } ) ; onUnmounted ( ( ) => { window . removeEventListener ( 'scroll' , onScroll ) ; } ) ; } function getSidebarLinks ( ) { return [ ] . slice . call ( document . querySelectorAll ( '.sidebar a.sidebar-link-item' ) ) ; } function getAnchors ( sidebarLinks ) { return [ ] . slice . call ( document . querySelectorAll ( '.header-anchor' ) ) . filter ( ( anchor ) => sidebarLinks . some ( ( sidebarLink ) => sidebarLink . hash === anchor . hash ) ) as HTMLAnchorElement [ ] ; } function getPageOffset ( ) { return ( document . querySelector ( '.nav-bar' ) as HTMLElement ) . offsetHeight ; } function getAnchorTop ( anchor ) { const pageOffset = getPageOffset ( ) ; return anchor . parentElement ! . offsetTop - pageOffset - 15 ; } function isAnchorActive ( index , anchor , nextAnchor ) { const scrollTop = window . scrollY ; if ( index === 0 && scrollTop === 0 ) { return [ true , null ] ; } if ( scrollTop < getAnchorTop ( anchor ) ) { return [ false , null ] ; } if ( ! nextAnchor || scrollTop < getAnchorTop ( nextAnchor ) ) { return [ true , decodeURIComponent ( anchor . hash ) ] ; } return [ false , null ] ; } function throttleAndDebounce ( fn , delay ) { let timeout ; let called = false ; return ( ) => { if ( timeout ) { clearTimeout ( timeout ) ; } if ( ! called ) { fn ( ) ; called = true ; setTimeout ( ( ) => { called = false ; } , delay ) ; } else { timeout = setTimeout ( fn , delay ) ; } } ; }