feat: enhance table of contents with active section highlighting and debounced scroll handling

pull/6052/head
Yunus Emre Alpu 21 hours ago
parent 5e4716f56a
commit a55d00fcdf

@ -96,12 +96,22 @@
.overline.pa-5.pb-0(:class='$vuetify.theme.dark ? `blue--text text--lighten-2` : `primary--text`') {{$t('common:page.toc')}}
v-list.pb-3(dense, nav, :class='$vuetify.theme.dark ? `darken-3-d3` : ``')
template(v-for='(tocItem, tocIdx) in tocDecoded')
v-list-item(@click='$vuetify.goTo(tocItem.anchor, scrollOpts)', :id='tocItem.anchor')
v-list-item(
:key='`toc-${tocIdx}`'
@click='$vuetify.goTo(tocItem.anchor, scrollOpts)'
:id='tocItem.anchor'
:class='{ titleactive: activeTocId === tocItem.anchor }'
)
v-icon(color='grey', small) {{ $vuetify.rtl ? `mdi-chevron-left` : `mdi-chevron-right` }}
v-list-item-title.px-3 {{tocItem.title}}
//- v-divider(v-if='tocIdx < toc.length - 1 || tocItem.children.length')
template(v-for='tocSubItem in tocItem.children')
v-list-item(@click='$vuetify.goTo(tocSubItem.anchor, scrollOpts)', :id='tocSubItem.anchor')
template(v-for='(tocSubItem, tocSubIdx) in tocItem.children')
v-list-item(
:key='`toc-${tocIdx}-${tocSubIdx}`'
@click='$vuetify.goTo(tocSubItem.anchor, scrollOpts)'
:id='tocSubItem.anchor'
:class='{ titleactive: activeTocId === tocSubItem.anchor }'
)
v-icon.px-3(color='grey lighten-1', small) {{ $vuetify.rtl ? `mdi-chevron-left` : `mdi-chevron-right` }}
v-list-item-title.px-3.caption.grey--text(:class='$vuetify.theme.dark ? `text--lighten-1` : `text--darken-1`') {{tocSubItem.title}}
//- v-divider(inset, v-if='tocIdx < toc.length - 1')
@ -520,7 +530,9 @@ export default {
}
}
},
winWidth: 0
winWidth: 0,
activeTocId: null,
debouncedScrollHandler: null
}
},
computed: {
@ -600,7 +612,8 @@ export default {
}
this.$store.set('page/mode', 'view')
window.addEventListener('scroll', this.handleScroll)
this.debouncedScrollHandler = _.debounce(this.handleScroll, 100)
window.addEventListener('scroll', this.debouncedScrollHandler, { passive: true })
},
mounted () {
if (this.$vuetify.theme.dark) {
@ -645,11 +658,19 @@ export default {
}
})
if (this.tocDecoded.length) {
this.handleScroll()
}
window.boot.notify('page-ready')
})
},
destroyed () {
window.removeEventListener('scroll', this.handleScroll)
beforeDestroy () {
// Properly remove the debounced scroll handler
if (this.debouncedScrollHandler) {
window.removeEventListener('scroll', this.debouncedScrollHandler)
this.debouncedScrollHandler = null
}
},
methods: {
goHome () {
@ -674,23 +695,24 @@ export default {
},
// Highlight the current section items in a sticky table of contents as you scroll down the page.
handleScroll () {
if (!this.$refs.container) return
const scrollPosition = window.scrollY
const sections = document.querySelectorAll('h1, h2')
const links = document.querySelectorAll('.v-list-item--link') // .v-list-item--link .v-list-item__title
const current = []
sections.forEach((el) => {
if (el.offsetTop <= scrollPosition + 5) {
current.push(el)
const offset = 100
const sections = this.$refs.container.querySelectorAll('h1, h2')
let activeSection = null
for (let i = sections.length - 1; i >= 0; i--) {
const section = sections[i]
if (section.offsetTop <= scrollPosition + offset) {
activeSection = section
break
}
})
const currentSection = current[current.length - 1]
const id = currentSection && currentSection.id
links.forEach((el) => {
el.classList.remove('titleactive')
if (el.getAttribute('id') === `#${id}`) {
el.classList.add('titleactive')
}
})
}
const id = activeSection?.id
this.activeTocId = id ? `#${id}` : null
},
pageEdit () {
this.$root.$emit('pageEdit')

Loading…
Cancel
Save