feat(nav): complete remaining zibll parity (search, i18n, senior menus, CSS)
- Remove obsolete dual-path nav CSS; add utilities + language dropdown styles - Wire header/mobile translate switcher from i18n config - Layout-2 inline search hot keywords panel; nav_folding on resize - NavMenuOptions JSON editors for graphic_card / multi_column_links - Section-navs zibll posts-nav-lists; page-navs enqueue; mobile article TOC box - SPA/PHP user-card publish type; rebuild admin assets
This commit is contained in:
1 parent
259dbdbe04
commit
fc50e57bc9
22 files changed
+580
-1994
No files matched your search
+51
-24
@@ -1,13 +1,15 @@
|
||||
/**
|
||||
* AKSRT nav chrome — zibll contract (body.mobile-navbar-show, folding, chevron).
|
||||
* Drawer open/close is handled by aksrt-runtime [data-toggle-class].
|
||||
* AKSRT nav chrome — zibll contract (folding, chevron, language, drawer a11y).
|
||||
* Drawer open/close via aksrt-runtime [data-toggle-class] → body.mobile-navbar-show.
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function appendChevrons() {
|
||||
document.querySelectorAll(".navbar-top li.menu-item-has-children > a").forEach(function (a) {
|
||||
if (a.querySelector(".fa-angle-down")) return;
|
||||
var foldTimer = null;
|
||||
|
||||
function appendChevrons(root) {
|
||||
(root || document).querySelectorAll(".navbar-top li.menu-item-has-children > a").forEach(function (a) {
|
||||
if (a.querySelector(".fa-angle-down, .fas.fa-angle-down")) return;
|
||||
var i = document.createElement("i");
|
||||
i.className = "fa fa-angle-down ml6";
|
||||
i.setAttribute("aria-hidden", "true");
|
||||
@@ -15,18 +17,30 @@
|
||||
});
|
||||
}
|
||||
|
||||
function clearFolding() {
|
||||
var more = document.querySelector(".navbar-nav > li.aksrt-nav-more");
|
||||
if (!more) return;
|
||||
var sub = more.querySelector(".sub-menu");
|
||||
var nav = more.parentNode;
|
||||
if (sub && nav) {
|
||||
while (sub.firstChild) {
|
||||
nav.insertBefore(sub.firstChild, more);
|
||||
}
|
||||
}
|
||||
more.remove();
|
||||
}
|
||||
|
||||
function navFolding() {
|
||||
var collapse = document.querySelector(".navbar-collapse");
|
||||
var nav = document.querySelector(".navbar-nav");
|
||||
if (!collapse || !nav) return;
|
||||
if (window.innerWidth <= 996) return;
|
||||
|
||||
clearFolding();
|
||||
|
||||
if (window.innerWidth <= 996) return;
|
||||
var header = document.querySelector(".header");
|
||||
if (header && header.offsetHeight <= 81) return;
|
||||
|
||||
// Already folded?
|
||||
if (nav.querySelector("li.aksrt-nav-more")) return;
|
||||
|
||||
var surplus = collapse.clientWidth;
|
||||
Array.prototype.forEach.call(collapse.children, function (child) {
|
||||
if (!child.classList.contains("navbar-nav")) {
|
||||
@@ -40,9 +54,7 @@
|
||||
var foldItems = [];
|
||||
Array.prototype.forEach.call(nav.children, function (li) {
|
||||
navsWidth += li.offsetWidth || 0;
|
||||
if (navsWidth > surplus) {
|
||||
foldItems.push(li);
|
||||
}
|
||||
if (navsWidth > surplus) foldItems.push(li);
|
||||
});
|
||||
if (!foldItems.length) return;
|
||||
|
||||
@@ -51,14 +63,33 @@
|
||||
more.innerHTML =
|
||||
'<a href="javascript:void(0);"><i class="fas fa-bars" aria-hidden="true"></i></a><ul class="sub-menu"></ul>';
|
||||
var sub = more.querySelector(".sub-menu");
|
||||
foldItems.forEach(function (li) {
|
||||
sub.appendChild(li);
|
||||
});
|
||||
foldItems.forEach(function (li) { sub.appendChild(li); });
|
||||
nav.appendChild(more);
|
||||
appendChevrons(more);
|
||||
}
|
||||
|
||||
function scheduleFold() {
|
||||
if (foldTimer) clearTimeout(foldTimer);
|
||||
foldTimer = setTimeout(function () {
|
||||
try { navFolding(); } catch (e) {}
|
||||
}, 80);
|
||||
}
|
||||
|
||||
function bindLanguage() {
|
||||
document.addEventListener("click", function (e) {
|
||||
var btn = e.target && e.target.closest ? e.target.closest(".translate-button[data-language]") : null;
|
||||
if (!btn) return;
|
||||
e.preventDefault();
|
||||
var lang = btn.getAttribute("data-language");
|
||||
if (!lang) return;
|
||||
try { localStorage.setItem("aksrt_lang", lang); } catch (err) {}
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.set("lang", lang);
|
||||
window.location.href = url.toString();
|
||||
});
|
||||
}
|
||||
|
||||
function bindDrawerA11y() {
|
||||
// Esc closes mobile nav via body class.
|
||||
document.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Escape" && document.body.classList.contains("mobile-navbar-show")) {
|
||||
document.body.classList.remove("mobile-navbar-show");
|
||||
@@ -68,18 +99,14 @@
|
||||
|
||||
function init() {
|
||||
appendChevrons();
|
||||
try {
|
||||
navFolding();
|
||||
} catch (err) {
|
||||
// ignore fold failures
|
||||
}
|
||||
scheduleFold();
|
||||
bindLanguage();
|
||||
bindDrawerA11y();
|
||||
window.addEventListener("resize", scheduleFold, { passive: true });
|
||||
}
|
||||
|
||||
if (window.AksrtFrontend && typeof window.AksrtFrontend.register === "function") {
|
||||
window.AksrtFrontend.register("aksrt-nav-chrome", function () {
|
||||
init();
|
||||
});
|
||||
window.AksrtFrontend.register("aksrt-nav-chrome", function () { init(); });
|
||||
} else if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
|
||||
+127
-155
@@ -1,9 +1,7 @@
|
||||
/**
|
||||
* AKSRT Section Navs — Article TOC / Scrollspy
|
||||
* AKSRT Section Navs — zibll section_navs parity (posts-nav-lists + 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.
|
||||
* Builds TOC from [data-nav] h1–h4 into .posts-nav-box / .posts-nav-lists.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
@@ -12,164 +10,138 @@
|
||||
|
||||
var HEADING_SEL = 'h1, h2, h3, h4';
|
||||
var MIN_HEADINGS = 2;
|
||||
var SCROLL_OFFSET = 105;
|
||||
var MAX_HEIGHT_COLLAPSE = 380;
|
||||
var NAVBOX = '.posts-nav-box';
|
||||
var NAV_CLASS = 'posts-nav-lists';
|
||||
|
||||
function initSectionNavs() {
|
||||
var navContainer = document.querySelector('[data-nav]');
|
||||
if (!navContainer) return;
|
||||
function isExcluded(el) {
|
||||
return el.classList.contains('item-heading') || !!el.closest('.no-nav');
|
||||
}
|
||||
|
||||
// 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')) {
|
||||
function ensureId(el, index) {
|
||||
if (!el.id) {
|
||||
el.id = 'section-nav-' + 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;
|
||||
}
|
||||
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>'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
var top = el.getBoundingClientRect().top + window.pageYOffset - offset;
|
||||
window.scrollTo({ top: top, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
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 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 });
|
||||
});
|
||||
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 init() {
|
||||
if (document.querySelector('.posts-nav-box .toc-list, .posts-nav-box.toc-widget .toc-list')) {
|
||||
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 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();
|
||||
scrollspy();
|
||||
autoHash();
|
||||
}
|
||||
|
||||
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 });
|
||||
if (window.AksrtFrontend && typeof window.AksrtFrontend.register === 'function') {
|
||||
window.AksrtFrontend.register('aksrt-section-navs', function () {
|
||||
init();
|
||||
});
|
||||
|
||||
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 = '<i class="fas fa-chevron-right"></i>';
|
||||
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 if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
initSectionNavs();
|
||||
init();
|
||||
}
|
||||
})();
|
||||
Reference in new issue
Block a user