fix(widgets): restore article table of contents behavior and styles

This commit is contained in:
kate522 committed 2026-10-30 13:18:40 +08:00
1 parent fc04bf65e6
commit ea80cd96cc
15 files changed
+965 -407

No files matched your search

+158 -113
View File
@@ -1,141 +1,186 @@
/**
* AKSRT Section Navs — legacy section_navs parity (posts-nav-lists + scrollspy).
*
* Builds TOC from [data-nav] h1–h4 into .posts-nav-box / .posts-nav-lists.
*
* @package AKSRT
*/
/** Article directory: one renderer for sidebar, affix and mobile copies. */
(function () {
'use strict';
var HEADING_SEL = 'h1, h2, h3, h4';
var MIN_HEADINGS = 3;
var NAVBOX = '.posts-nav-box';
var NAV_CLASS = 'posts-nav-lists';
var entries = [];
var rendered = new WeakMap();
var framePending = false;
var handledHash = '';
function isExcluded(el) {
return el.classList.contains('item-heading') || !!el.closest('.no-nav');
function hashId(hash) {
try { return decodeURIComponent(hash.slice(1)); }
catch (error) { return ''; }
}
function ensureId(el, index) {
if (!el.id) {
el.id = 'wznav_' + index;
}
return el.id;
}
function buildLists(root) {
var headings = root.querySelectorAll(HEADING_SEL);
var html = '';
var count = 0;
headings.forEach(function (h, i) {
if (isExcluded(h)) return;
var id = ensureId(h, i);
var tag = h.tagName.toUpperCase();
var text = (h.textContent || '').trim();
if (!text) return;
html += '<li class="n-' + tag + '"><a href="#' + id + '">' + text + '</a></li>';
count++;
});
return count >= MIN_HEADINGS ? html : '';
}
function fillBoxes(listsHtml) {
document.querySelectorAll(NAVBOX).forEach(function (box) {
var existing = box.querySelector('.' + NAV_CLASS);
if (existing) {
existing.innerHTML = listsHtml;
return;
function collectHeadings() {
var found = [];
document.querySelectorAll('[data-nav] h1, [data-nav] h2, [data-nav] h3, [data-nav] h4').forEach(function (heading, index) {
var text = (heading.textContent || '').trim();
if (!text || heading.classList.contains('item-heading') || heading.closest('.no-nav')) return;
if (!heading.id || document.getElementById(heading.id) !== heading) {
var id = 'wznav_' + index;
var suffix = 0;
while (document.getElementById(id)) id = 'wznav_' + index + '_' + (++suffix);
heading.id = id;
}
var title = box.getAttribute('data-title') || '';
var titleHtml = title
? '<div class="box-body notop"><div class="title-theme">' + title + '</div></div>'
: '';
box.insertAdjacentHTML(
'beforeend',
titleHtml +
'<div class="zib-widget"><div class="' +
NAV_CLASS +
' scroll-y mini-scrollbar list-unstyled">' +
listsHtml +
'</div></div>'
);
found.push({ element: heading, id: heading.id, text: text, tag: heading.tagName.toUpperCase() });
});
return found;
}
function smoothTo(hash) {
var el = document.querySelector(hash);
if (!el) return;
var offset = 0;
var header = document.querySelector('.header');
if (document.body.classList.contains('nav-fixed') && header) {
offset = header.offsetHeight + 12;
function groupChildren(item) {
var children = [];
var next = item.nextElementSibling;
while (next && !next.classList.contains('n-H1') && !next.classList.contains('n-H2')) {
children.push(next);
next = next.nextElementSibling;
}
var top = el.getBoundingClientRect().top + window.pageYOffset - offset;
window.scrollTo({ top: top, behavior: 'smooth' });
return children;
}
function bindClicks() {
document.addEventListener('click', function (e) {
var a = e.target && e.target.closest ? e.target.closest('.' + NAV_CLASS + ' a[href^="#"]') : null;
if (!a) return;
var href = a.getAttribute('href');
if (!href || href.length < 2) return;
e.preventDefault();
history.replaceState(null, '', href);
smoothTo(href);
function collapseGroups(list) {
if (list.scrollHeight <= 380 || list.querySelector('.nav-toggle-collapse')) return;
list.querySelectorAll('.n-H1, .n-H2').forEach(function (item) {
var children = groupChildren(item);
if (!children.length) return;
children.forEach(function (child) { child.classList.add('yc'); });
var button = document.createElement('button');
button.type = 'button';
button.className = 'fa fas fa-angle-right nav-toggle-collapse';
button.setAttribute('aria-expanded', 'false');
button.setAttribute('aria-label', item.querySelector('a').textContent);
item.appendChild(button);
});
}
function fillBox(box, signature) {
if (entries.length < 3) {
if (box.dataset.aksrtSectionNav === '1') {
box.replaceChildren();
delete box.dataset.aksrtSectionNav;
rendered.delete(box);
}
return;
}
if (rendered.get(box) === signature) {
var current = box.querySelector('.bl');
if (current) collapseGroups(current);
return;
}
box.replaceChildren();
box.dataset.aksrtSectionNav = '1';
rendered.set(box, signature);
var title = box.getAttribute('data-title') || '';
if (title) {
var titleBody = document.createElement('div');
titleBody.className = 'box-body notop';
var titleText = document.createElement('div');
titleText.className = 'title-theme';
// data-title is escaped by the PHP renderer, including its optional small tag.
titleText.innerHTML = title;
titleBody.appendChild(titleText);
box.appendChild(titleBody);
}
var widget = document.createElement('div');
widget.className = 'zib-widget';
var nav = document.createElement('div');
nav.className = 'posts-nav-lists scroll-y mini-scrollbar list-unstyled';
var list = document.createElement('ul');
list.className = 'bl relative nav';
entries.forEach(function (entry) {
var item = document.createElement('li');
item.className = 'n-' + entry.tag;
var link = document.createElement('a');
link.className = 'text-ellipsis';
link.setAttribute('href', '#' + encodeURIComponent(entry.id));
link.textContent = entry.text;
item.appendChild(link);
list.appendChild(item);
});
nav.appendChild(list);
widget.appendChild(nav);
box.appendChild(widget);
collapseGroups(list);
}
function scrollspy() {
var links = document.querySelectorAll('.' + NAV_CLASS + ' a[href^="#"]');
if (!links.length) return;
var map = [];
links.forEach(function (a) {
var id = a.getAttribute('href').slice(1);
var el = document.getElementById(id);
if (el) map.push({ el: el, a: a, li: a.parentElement });
if (entries.length < 3) return;
var current = '';
entries.forEach(function (entry) {
if (!entry.element.getClientRects().length) return;
if (entry.element.getBoundingClientRect().top <= 105) current = entry.id;
});
document.querySelectorAll('.posts-nav-lists a[href^="#"]').forEach(function (link) {
var active = hashId(link.getAttribute('href')) === current;
link.parentElement.classList.toggle('active', active);
if (active) link.setAttribute('aria-current', 'location');
else link.removeAttribute('aria-current');
});
function update() {
var y = window.scrollY + 110;
var current = null;
for (var i = 0; i < map.length; i++) {
if (map[i].el.offsetTop <= y) current = map[i];
}
map.forEach(function (m) {
if (m.li) m.li.classList.toggle('active', m === current);
});
}
window.addEventListener('scroll', update, { passive: true });
update();
}
function autoHash() {
if (window.location.hash) {
setTimeout(function () {
smoothTo(window.location.hash);
}, 40);
}
function scheduleSpy() {
if (framePending) return;
framePending = true;
window.requestAnimationFrame(function () {
framePending = false;
scrollspy();
});
}
function scrollToHeading(target) {
// Reference behavior: reveal content hidden by an accordion or height limit.
var panels = jQuery(target).parents('.panel').find('.collapse');
if (typeof panels.collapse === 'function') panels.collapse('show');
document.querySelectorAll('.read-more-open').forEach(function (button) { button.click(); });
document.body.classList.remove('mobile-navbar-show');
var header = document.querySelector('.header');
var offset = document.body.classList.contains('nav-fixed') && header ? header.offsetHeight + 20 : 0;
var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
window.scrollTo({ top: Math.max(0, target.getBoundingClientRect().top + window.pageYOffset - offset), behavior: reduced ? 'auto' : 'smooth' });
scheduleSpy();
}
function followHash() {
var hash = window.location.hash;
if (!hash || hash === handledHash) return;
var id = hashId(hash);
var entry = entries.find(function (item) { return item.id === id; });
if (!entry || entries.length < 3) return;
handledHash = hash;
window.setTimeout(function () { scrollToHeading(entry.element); }, 20);
}
function init() {
if (document.documentElement.dataset.aksrtSectionNavInitialized === '1') return;
document.documentElement.dataset.aksrtSectionNavInitialized = '1';
if (document.querySelector('.posts-nav-box .toc-list, .posts-nav-box.toc-widget .toc-list')) {
return;
}
var root = document.querySelector('[data-nav], .article-content, .entry-content, main.article');
if (!root) return;
var listsHtml = buildLists(root);
if (!listsHtml) return;
if (!document.querySelector(NAVBOX) && !document.querySelector('.' + NAV_CLASS)) {
return;
}
fillBoxes(listsHtml);
bindClicks();
entries = collectHeadings();
var signature = JSON.stringify(entries.map(function (entry) { return [entry.id, entry.text, entry.tag]; }));
document.querySelectorAll('.posts-nav-box').forEach(function (box) {
fillBox(box, signature + (box.getAttribute('data-title') || ''));
});
scrollspy();
autoHash();
followHash();
}
document.addEventListener('click', function (event) {
var button = event.target.closest('.posts-nav-lists .nav-toggle-collapse');
if (button) {
event.preventDefault();
var expanded = button.getAttribute('aria-expanded') !== 'true';
button.setAttribute('aria-expanded', String(expanded));
button.classList.toggle('fa-rotate-90', expanded);
groupChildren(button.parentElement).forEach(function (child) { child.classList.toggle('yc', !expanded); });
return;
}
var link = event.target.closest('.posts-nav-lists a[href^="#"]');
if (!link) return;
var target = document.getElementById(hashId(link.getAttribute('href')));
if (!target) return;
event.preventDefault();
scrollToHeading(target);
});
window.addEventListener('scroll', scheduleSpy, { passive: true });
window.addEventListener('resize', init, { passive: true });
window.addEventListener('hashchange', followHash);
jQuery(document).off('auto_fun.aksrt-section-navs').on('auto_fun.aksrt-section-navs', init);
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init, { once: true });
else init();
})();