/** * AKSRT Section Navs — Article TOC / Scrollspy * * Auto-generates a table of contents from h1~h4 headings within * [data-nav] content areas. Scrollspy highlights the current section. * Collapsible sub-items when the TOC is tall. * * @package AKSRT */ (function () { 'use strict'; var HEADING_SEL = 'h1, h2, h3, h4'; var MIN_HEADINGS = 2; var SCROLL_OFFSET = 105; var MAX_HEIGHT_COLLAPSE = 380; function initSectionNavs() { var navContainer = document.querySelector('[data-nav]'); if (!navContainer) return; // Single-post TOC is owned by page-template.js (.posts-nav-box .toc-list). // Do not invent a second .aksrt-section-nav tree on article singles. if (document.querySelector('.posts-nav-box .toc-list, .posts-nav-box.toc-widget')) { return; } var headings = navContainer.querySelectorAll(HEADING_SEL); if (headings.length < MIN_HEADINGS) return; var tocContainer = document.querySelector('.posts-nav-lists, .aksrt-section-nav'); if (!tocContainer) { // Docs / page-navs only: create a dedicated container. Never inject into single sidebars. if (document.body.classList.contains('single') || document.body.classList.contains('single-post')) { return; } tocContainer = document.createElement('nav'); tocContainer.className = 'aksrt-section-nav'; tocContainer.setAttribute('aria-label', '文章目录'); var sidebar = document.querySelector('.content-layout__aside, .sidebar, .widget-area'); if (sidebar) { sidebar.insertBefore(tocContainer, sidebar.firstChild); } else if (navContainer.parentNode) { navContainer.parentNode.insertBefore(tocContainer, navContainer); } else { return; } } var list = document.createElement('ul'); list.className = 'aksrt-section-nav__list'; var items = []; headings.forEach(function (heading, i) { if (!heading.id) { heading.id = 'section-nav-' + i; } var level = parseInt(heading.tagName.charAt(1), 10); var li = document.createElement('li'); li.className = 'aksrt-section-nav__item aksrt-section-nav__item--h' + level; var link = document.createElement('a'); link.href = '#' + heading.id; link.textContent = heading.textContent.trim(); link.className = 'aksrt-section-nav__link'; link.addEventListener('click', function (e) { e.preventDefault(); smoothScrollTo(heading); // Expand collapsed panels containing the target var panel = heading.closest('[data-collapse], .collapse'); if (panel) { panel.classList.add('show', 'is-open'); var readMore = panel.previousElementSibling; if (readMore && readMore.classList.contains('read-more-open')) readMore.click(); } }); li.appendChild(link); list.appendChild(li); items.push({ el: heading, link: link, li: li, level: level }); }); tocContainer.innerHTML = ''; tocContainer.appendChild(list); // Collapse sub-items if TOC is tall if (tocContainer.scrollHeight > MAX_HEIGHT_COLLAPSE) { collapseSubItems(list, items); } // Scrollspy var ticking = false; function onScroll() { if (ticking) return; ticking = true; requestAnimationFrame(function () { ticking = false; var scrollTop = window.pageYOffset + SCROLL_OFFSET; var activeIdx = 0; for (var i = items.length - 1; i >= 0; i--) { if (items[i].el.offsetTop <= scrollTop) { activeIdx = i; break; } } items.forEach(function (item, idx) { item.link.classList.toggle('is-active', idx === activeIdx); }); }); } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); // URL hash auto-scroll if (window.location.hash) { var target = document.querySelector(window.location.hash); if (target) setTimeout(function () { smoothScrollTo(target); }, 100); } } function smoothScrollTo(el) { if (window.aksrt && aksrt.scrollTopTo) { aksrt.scrollTopTo(el); } else { var top = el.getBoundingClientRect().top + window.pageYOffset - SCROLL_OFFSET; window.scrollTo({ top: Math.max(0, top), behavior: 'smooth' }); } } function collapseSubItems(list, items) { var parentStack = []; items.forEach(function (item) { while (parentStack.length && parentStack[parentStack.length - 1].level >= item.level) { parentStack.pop(); } if (item.level >= 3 && parentStack.length) { item.li.classList.add('yc'); // Add collapse toggle to parent if not already there var parentLi = parentStack[parentStack.length - 1].li; if (!parentLi.querySelector('.nav-toggle-collapse')) { var toggle = document.createElement('span'); toggle.className = 'nav-toggle-collapse'; toggle.innerHTML = ''; toggle.addEventListener('click', function (e) { e.stopPropagation(); toggle.classList.toggle('fa-rotate-90'); var next = parentLi.nextElementSibling; while (next && next.classList.contains('yc')) { next.classList.toggle('yc-open'); next = next.nextElementSibling; } }); parentLi.appendChild(toggle); } } parentStack.push(item); }); } if (window.AksrtFrontend) { AksrtFrontend.register('aksrt-section-navs', function () { initSectionNavs(); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initSectionNavs); } else { initSectionNavs(); } })();