initial commit

This commit is contained in:
2026-07-21 10:00:08 +07:00
commit 7344144e20
282 changed files with 41294 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
(function () {
const nav = document.getElementById('sidebar-nav');
const wrap = document.getElementById('sidebar-nav-wrap');
const track = document.getElementById('sb-track');
const thumb = document.getElementById('sb-thumb');
if (!nav || !track || !thumb || !wrap) return;
let isDragging = false;
function show() { track.style.opacity = '1'; }
function hide() { if (!isDragging) track.style.opacity = '0'; }
function update() {
const sh = nav.scrollHeight, ch = nav.clientHeight;
if (sh <= ch) { track.style.display = 'none'; return; }
track.style.display = 'block'; track.style.opacity = '1';
const st = nav.scrollTop, th = Math.max((ch / sh) * ch, 24);
const maxTop = ch - th, ratio = sh > ch ? st / (sh - ch) : 0;
thumb.style.height = th + 'px'; thumb.style.top = (ratio * maxTop) + 'px';
}
const observer = new MutationObserver(function () {
update(); setTimeout(function () {
update();
if (nav.scrollHeight > nav.clientHeight) { track.style.display = 'block'; track.style.opacity = '1'; }
}, 350);
});
observer.observe(nav, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });
nav.addEventListener('scroll', update); window.addEventListener('resize', update);
wrap.addEventListener('mouseenter', function () { show(); update(); });
wrap.addEventListener('mouseleave', hide);
thumb.addEventListener('mousedown', function (e) {
e.preventDefault(); isDragging = true; track.style.opacity = '1';
document.body.style.cursor = 'pointer'; document.body.style.userSelect = 'none';
function onMove(ev) {
if (!isDragging) return;
const rect = track.getBoundingClientRect();
nav.scrollTop = ((ev.clientY - rect.top) / rect.height) * (nav.scrollHeight - nav.clientHeight);
}
function onUp() {
isDragging = false; document.body.style.cursor = ''; document.body.style.userSelect = '';
document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp);
}
document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp);
});
update(); setTimeout(update, 300);
nav.querySelectorAll('.submenu-group:not(.open) > .submenu-body').forEach(function (b) {
b.style.maxHeight = '0px';
});
function setBodyHeight(group) {
const body = group.querySelector(':scope > .submenu-body');
if (!body) return;
if (group.classList.contains('open')) {
body.style.maxHeight = body.scrollHeight + 'px';
} else {
body.style.maxHeight = '0px';
}
}
nav.addEventListener('transitionend', function (e) {
if (!e.target.classList || !e.target.classList.contains('submenu-body')) return;
if (e.propertyName !== 'max-height') return;
if (e.target.style.maxHeight !== '0px') {
e.target.style.maxHeight = 'none';
}
});
nav.addEventListener('click', function (e) {
const head = e.target.closest('.submenu-head');
if (!head) return;
const group = head.parentElement;
if (group && group.classList.contains('submenu-group')) {
group.classList.toggle('open');
setBodyHeight(group);
update();
}
});
function activateCurrent() {
const currentPage = window.location.pathname; // includes leading slash, matches href="/..."
if (!currentPage) return;
const activeLink = nav.querySelector('a[href="' + currentPage + '"]');
if (!activeLink) return;
activeLink.classList.add('active');
let node = activeLink.parentElement;
while (node && node !== nav) {
if (node.classList && node.classList.contains('submenu-group')) {
node.classList.add('open');
setBodyHeight(node);
}
node = node.parentElement;
}
update();
requestAnimationFrame(function () { activeLink.scrollIntoView({ block: 'nearest' }); });
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', activateCurrent);
} else {
activateCurrent();
}
})();