www/themes/blowfish/layouts/partials/toc.html
2025-07-18 21:45:32 +02:00

107 lines
4 KiB
HTML

<details
open
id="TOCView"
class="toc-right mt-0 overflow-y-auto overscroll-contain scrollbar-thin scrollbar-track-neutral-200 scrollbar-thumb-neutral-400 dark:scrollbar-track-neutral-800 dark:scrollbar-thumb-neutral-600 rounded-lg ltr:-ml-5 ltr:pl-5 rtl:-mr-5 rtl:pr-5 hidden lg:block">
<summary
class="block py-1 text-lg font-semibold cursor-pointer bg-neutral-100 text-neutral-800 ltr:-ml-5 ltr:pl-5 rtl:-mr-5 rtl:pr-5 dark:bg-neutral-700 dark:text-neutral-100 lg:hidden">
{{ i18n "article.table_of_contents" }}
</summary>
<div
class="min-w-[220px] py-2 border-dotted ltr:-ml-5 ltr:border-l ltr:pl-5 rtl:-mr-5 rtl:border-r rtl:pr-5 dark:border-neutral-600">
{{ .TableOfContents | emojify }}
</div>
</details>
<details class="toc-inside mt-0 overflow-hidden rounded-lg ltr:-ml-5 ltr:pl-5 rtl:-mr-5 rtl:pr-5 lg:hidden">
<summary
class="py-1 text-lg font-semibold cursor-pointer bg-neutral-100 text-neutral-800 ltr:-ml-5 ltr:pl-5 rtl:-mr-5 rtl:pr-5 dark:bg-neutral-700 dark:text-neutral-100 lg:hidden">
{{ i18n "article.table_of_contents" }}
</summary>
<div
class="py-2 border-dotted border-neutral-300 ltr:-ml-5 ltr:border-l ltr:pl-5 rtl:-mr-5 rtl:border-r rtl:pr-5 dark:border-neutral-600">
{{ .TableOfContents | emojify }}
</div>
</details>
{{ if .Site.Params.smartTOC }}
<script>
(function () {
'use strict'
const SCROLL_OFFSET_RATIO = 0.33
const TOC_SELECTOR = '#TableOfContents'
const ANCHOR_SELECTOR = '.anchor'
const TOC_LINK_SELECTOR = 'a[href^="#"]'
const NESTED_LIST_SELECTOR = 'li ul'
const ACTIVE_CLASS = 'active'
function getActiveAnchorId(anchors, offsetRatio) {
const threshold = window.scrollY + window.innerHeight * offsetRatio
const tocLinks = [...document.querySelectorAll('#TableOfContents a[href^="#"]')]
const tocIds = new Set(tocLinks.map(link => link.getAttribute('href').substring(1)))
for (let i = anchors.length - 1; i >= 0; i--) {
const top = anchors[i].getBoundingClientRect().top + window.scrollY
if (top <= threshold && tocIds.has(anchors[i].id)) {
return anchors[i].id
}
}
return anchors.find(anchor => tocIds.has(anchor.id))?.id || ''
}
function updateTOC({ toc, anchors, links, scrollOffset, collapseInactive }) {
const activeId = getActiveAnchorId(anchors, scrollOffset)
if (!activeId) return
links.forEach(link => {
const isActive = link.getAttribute('href') === `#${activeId}`
link.classList.toggle(ACTIVE_CLASS, isActive)
if (collapseInactive) {
const ul = link.closest('li')?.querySelector('ul')
if (ul) ul.style.display = isActive ? '' : 'none'
}
})
if (collapseInactive) {
const activeLink = toc.querySelector(`a[href="#${CSS.escape(activeId)}"]`)
let el = activeLink
while (el && el !== toc) {
if (el.tagName === 'UL') el.style.display = ''
if (el.tagName === 'LI') el.querySelector('ul')?.style.setProperty('display', '')
el = el.parentElement
}
}
}
function initTOC() {
const toc = document.querySelector(TOC_SELECTOR)
if (!toc) return
const collapseInactive = {{ if site.Params.smartTOCHideUnfocusedChildren }}true{{ else }}false{{ end }}
const anchors = [...document.querySelectorAll(ANCHOR_SELECTOR)]
const links = [...toc.querySelectorAll(TOC_LINK_SELECTOR)]
if (collapseInactive) {
toc.querySelectorAll(NESTED_LIST_SELECTOR).forEach(ul => ul.style.display = 'none')
}
const config = {
toc,
anchors,
links,
scrollOffset: SCROLL_OFFSET_RATIO,
collapseInactive
}
window.addEventListener('scroll', () => updateTOC(config), { passive: true })
window.addEventListener('hashchange', () => updateTOC(config), { passive: true })
updateTOC(config)
}
document.readyState === 'loading'
? document.addEventListener('DOMContentLoaded', initTOC)
: initTOC()
})()
</script>
{{ end }}