Initialize project with clean ignore rules
This commit is contained in:
143
layouts/partials/toc.html
Normal file
143
layouts/partials/toc.html
Normal file
@@ -0,0 +1,143 @@
|
||||
<details
|
||||
open
|
||||
id="TOCView"
|
||||
class="toc-right mt-0 overflow-y-auto overscroll-contain bf-scrollbar rounded-lg -ms-5 ps-5 pe-2 block lg:block">
|
||||
<summary
|
||||
class="block py-1 text-lg font-semibold cursor-pointer bg-neutral-100 text-neutral-800 -ms-5 ps-5 dark:bg-neutral-700 dark:text-neutral-100 lg:hidden">
|
||||
{{ i18n "article.table_of_contents" }}
|
||||
</summary>
|
||||
<div
|
||||
id="TableOfContents"
|
||||
class="min-w-[220px] py-2 border-dotted border-s-1 -ms-5 ps-5 dark:border-neutral-600">
|
||||
{{/* We use Hugo's native TOC generator so it never disappears */}}
|
||||
{{ .TableOfContents | safeHTML }}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="toc-inside mt-0 overflow-hidden rounded-lg -ms-5 ps-5 lg:hidden">
|
||||
<summary
|
||||
class="py-1 text-lg font-semibold cursor-pointer bg-neutral-100 text-neutral-800 -ms-5 ps-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 border-s-1 -ms-5 ps-5 dark:border-neutral-600">
|
||||
{{ .TableOfContents | safeHTML }}
|
||||
</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'
|
||||
let isJumpingToAnchor = false
|
||||
|
||||
// --- NEW LOGIC: Prune .no-toc items before initializing SmartTOC ---
|
||||
function pruneHiddenTOCItems() {
|
||||
// Find all headers on the page with the 'no-toc' class
|
||||
const ignoredHeaders = document.querySelectorAll('h1.no-toc, h2.no-toc, h3.no-toc, h4.no-toc, h5.no-toc, h6.no-toc');
|
||||
|
||||
ignoredHeaders.forEach(header => {
|
||||
if (!header.id) return;
|
||||
|
||||
// Find matching links in both desktop and mobile TOCs
|
||||
const linksToRemove = document.querySelectorAll(`.toc-right a[href="#${header.id}"], .toc-inside a[href="#${header.id}"]`);
|
||||
|
||||
linksToRemove.forEach(link => {
|
||||
const li = link.closest('li');
|
||||
if (li) {
|
||||
const parentUl = li.parentElement;
|
||||
li.remove(); // Remove the item from the TOC
|
||||
|
||||
// If removing this item leaves an empty list, remove the ul too
|
||||
if (parentUl && parentUl.children.length === 0) {
|
||||
parentUl.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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)))
|
||||
|
||||
if (isJumpingToAnchor) {
|
||||
for (let i = 0; i < anchors.length; i++) {
|
||||
const anchor = anchors[i]
|
||||
if (!tocIds.has(anchor.id)) continue
|
||||
const top = anchor.getBoundingClientRect().top + window.scrollY
|
||||
if (Math.abs(window.scrollY - top) < 100) {
|
||||
return anchor.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
// 1. Remove the hidden items first
|
||||
pruneHiddenTOCItems();
|
||||
|
||||
const toc = document.querySelector(TOC_SELECTOR)
|
||||
if (!toc) return
|
||||
const collapseInactive = {{ if site.Params.smartTOCHideUnfocusedChildren }}true{{ else }}false{{ end }}
|
||||
|
||||
// 2. Query the anchors, explicitly ignoring .no-toc headers so scroll-spy ignores them
|
||||
const anchors = [...document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]')].filter(a => !a.classList.contains('no-toc'))
|
||||
const links = [...toc.querySelectorAll(TOC_LINK_SELECTOR)]
|
||||
|
||||
if (collapseInactive) {
|
||||
toc.querySelectorAll(NESTED_LIST_SELECTOR).forEach(ul => ul.style.display = 'none')
|
||||
}
|
||||
|
||||
links.forEach(link => {
|
||||
link.addEventListener('click', () => { isJumpingToAnchor = true })
|
||||
})
|
||||
|
||||
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 }}
|
||||
Reference in New Issue
Block a user