Skip to content

Commit

Permalink
feat: active sidebar links
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jun 2, 2020
1 parent b1537a7 commit d2ea963
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 8 deletions.
2 changes: 1 addition & 1 deletion src/client/app/composables/siteData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function useSiteData() {

// hmr
if (import.meta.hot) {
import.meta.hot.acceptDeps('/@siteData', (m) => {
import.meta.hot!.acceptDeps('/@siteData', (m) => {
siteDataRef.value = parse(m.default)
})
}
2 changes: 1 addition & 1 deletion src/client/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function createApp() {

if (import.meta.hot) {
// hot reload pageData
import.meta.hot.on('vitepress:pageData', (data) => {
import.meta.hot!.on('vitepress:pageData', (data) => {
if (
data.path.replace(/(\bindex)?\.md$/, '') ===
location.pathname.replace(/(\bindex)?\.html$/, '')
Expand Down
4 changes: 2 additions & 2 deletions src/client/app/router.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { reactive, inject, markRaw } from 'vue'
import { reactive, inject, markRaw, nextTick } from 'vue'
import type { Component, InjectionKey } from 'vue'

export interface Route {
Expand Down Expand Up @@ -61,7 +61,7 @@ export function createRouter(
}
route.contentComponent = markRaw(comp)
if (inBrowser) {
setTimeout(() => {
nextTick(() => {
if (targetLoc.hash && !scrollPosition) {
const target = document.querySelector(
targetLoc.hash
Expand Down
3 changes: 3 additions & 0 deletions src/client/theme-default/components/SideBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useSiteData, usePageData, useRoute } from 'vitepress'
import { computed, h, FunctionalComponent } from 'vue'
import { Header } from '../../../../types/shared'
import { DefaultTheme } from '../config'
import { useActiveSidebarLinks } from '../composables/activeSidebarLink'

const SideBarItem: FunctionalComponent<{
item: ResolvedSidebarItem
Expand Down Expand Up @@ -30,6 +31,8 @@ export default {
const siteData = useSiteData()
const route = useRoute()

useActiveSidebarLinks()

const resolveSidebar = () => {
const {
headers,
Expand Down
16 changes: 13 additions & 3 deletions src/client/theme-default/components/SideBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,31 @@
.sidebar ul {
list-style-type: none;
line-height: 2;
padding-left: 1.5rem;
padding: 0;
margin: 0;
font-weight: 500;
}
.sidebar a {
display: inline-block;
color: var(--text-color);
padding-left: 1.5rem;
}
.sidebar a:hover {
color: var(--accent-color);
}
.sidebar a.active {
color: var(--accent-color);
font-weight: 500;
}
.sidebar > ul > li > a.active {
padding-left: 1.25rem;
border-left: .25rem solid var(--accent-color);
}
.sidebar ul ul {
font-weight: 400;
font-size: 0.9em;
padding-left: 1rem;
}
Expand Down
84 changes: 84 additions & 0 deletions src/client/theme-default/composables/activeSidebarLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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 activateLink = (hash: string) => {
deactiveLink(activeLink)
deactiveLink(rootActiveLink)
activeLink = document.querySelector(`.sidebar a[href="${hash}"]`)
if (activeLink) {
activeLink.classList.add('active')
// also add active class to parent h2 anchors
const rootLi = activeLink.closest('.sidebar > ul > li')
if (rootLi && rootLi !== activeLink.parentElement) {
rootActiveLink = rootLi.querySelector('a')
rootActiveLink && rootActiveLink.classList.add('active')
} else {
rootActiveLink = null
}
}
}

const setActiveLink = () => {
const sidebarLinks = [].slice.call(
document.querySelectorAll('.sidebar a')
) as HTMLAnchorElement[]

const anchors = [].slice
.call(document.querySelectorAll('.header-anchor'))
.filter((anchor: HTMLAnchorElement) =>
sidebarLinks.some((sidebarLink) => sidebarLink.hash === anchor.hash)
) as HTMLAnchorElement[]

const pageOffset = document.getElementById('app')!.offsetTop
const scrollTop = window.scrollY

const getAnchorTop = (anchor: HTMLAnchorElement): number =>
anchor.parentElement!.offsetTop - pageOffset - 15

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)))

if (isActive) {
const targetHash = decode(anchor.hash)
history.replaceState(null, document.title, targetHash)
activateLink(targetHash)
return
}
}
}

const onScroll = debounce(setActiveLink, 100)
onMounted(() => {
setActiveLink()
window.addEventListener('scroll', onScroll)
})

onUpdated(() => {
// sidebar update means a route change
activateLink(decode(location.hash))
})

onUnmounted(() => {
window.removeEventListener('scroll', onScroll)
})
}

function debounce(fn: () => void, delay: number): () => void {
let timeout: number
return () => {
if (timeout) clearTimeout(timeout)
timeout = setTimeout(fn, delay)
}
}
2 changes: 1 addition & 1 deletion src/client/theme-default/styles/layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ aside {
left: 0;
height: 100%;
width: var(--sidebar-width);
padding: calc(var(--header-height) + 1.5rem) 1.5rem 1.5rem 0;
padding: calc(var(--header-height) + 1.5rem) 0 1.5rem 0;
border-right: 1px solid var(--border-color);
background-color: #fff;
z-index: 3;
Expand Down

0 comments on commit d2ea963

Please sign in to comment.