mirror of
https://github.com/NotAShelf/nvf.git
synced 2026-02-04 10:55:50 +00:00
deploy: ab629c1995
This commit is contained in:
parent
c8e237c3a8
commit
3435dce176
13 changed files with 4554 additions and 5571 deletions
160
assets/main.js
160
assets/main.js
|
|
@ -1,8 +1,8 @@
|
|||
// Polyfill for requestIdleCallback for Safari and unsupported browsers
|
||||
if (typeof window.requestIdleCallback === "undefined") {
|
||||
window.requestIdleCallback = function (cb) {
|
||||
var start = Date.now();
|
||||
var idlePeriod = 50;
|
||||
const start = Date.now();
|
||||
const idlePeriod = 50;
|
||||
return setTimeout(function () {
|
||||
cb({
|
||||
didTimeout: false,
|
||||
|
|
@ -85,6 +85,127 @@ function createMobileElements() {
|
|||
}
|
||||
}
|
||||
|
||||
// Initialize collapsible sidebar sections with state persistence
|
||||
function initCollapsibleSections() {
|
||||
// Target sections in both desktop and mobile sidebars
|
||||
const sections = document.querySelectorAll(
|
||||
".sidebar .sidebar-section, .mobile-sidebar-content .sidebar-section",
|
||||
);
|
||||
|
||||
sections.forEach((section) => {
|
||||
const sectionId = section.dataset.section;
|
||||
if (!sectionId) return;
|
||||
|
||||
const storageKey = `sidebar-section-${sectionId}`;
|
||||
const savedState = localStorage.getItem(storageKey);
|
||||
|
||||
// Restore saved state (default is open)
|
||||
if (savedState === "closed") {
|
||||
section.removeAttribute("open");
|
||||
}
|
||||
|
||||
// Save state on toggle and sync between desktop/mobile
|
||||
section.addEventListener("toggle", () => {
|
||||
localStorage.setItem(storageKey, section.open ? "open" : "closed");
|
||||
|
||||
// Sync state between desktop and mobile versions
|
||||
const allWithSameSection = document.querySelectorAll(
|
||||
`.sidebar-section[data-section="${sectionId}"]`,
|
||||
);
|
||||
allWithSameSection.forEach((el) => {
|
||||
if (el !== section) {
|
||||
el.open = section.open;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize scroll spy
|
||||
function initScrollSpy() {
|
||||
const pageToc = document.querySelector(".page-toc");
|
||||
if (!pageToc) return;
|
||||
|
||||
const tocLinks = pageToc.querySelectorAll(".page-toc-list a");
|
||||
const content = document.querySelector(".content");
|
||||
if (!tocLinks.length || !content) return;
|
||||
|
||||
const headings = Array.from(
|
||||
content.querySelectorAll("h1[id], h2[id], h3[id]"),
|
||||
);
|
||||
|
||||
if (!headings.length) return;
|
||||
|
||||
// Build a map of heading IDs to TOC links for quick lookup
|
||||
const linkMap = new Map();
|
||||
tocLinks.forEach((link) => {
|
||||
const href = link.getAttribute("href");
|
||||
if (href && href.startsWith("#")) {
|
||||
linkMap.set(href.slice(1), link);
|
||||
}
|
||||
});
|
||||
|
||||
let activeLink = null;
|
||||
|
||||
// Update active link based on scroll position
|
||||
function updateActiveLink() {
|
||||
const threshold = 120; // threshold from the top of the viewport
|
||||
|
||||
let currentHeading = null;
|
||||
|
||||
// Find the last heading that is at or above the threshold
|
||||
for (const heading of headings) {
|
||||
const rect = heading.getBoundingClientRect();
|
||||
if (rect.top <= threshold) {
|
||||
currentHeading = heading;
|
||||
}
|
||||
}
|
||||
|
||||
// If no heading is above threshold, use first heading if it's in view
|
||||
if (!currentHeading && headings.length > 0) {
|
||||
const firstRect = headings[0].getBoundingClientRect();
|
||||
if (firstRect.top < window.innerHeight) {
|
||||
currentHeading = headings[0];
|
||||
}
|
||||
}
|
||||
|
||||
const newLink = currentHeading ? linkMap.get(currentHeading.id) : null;
|
||||
|
||||
if (newLink !== activeLink) {
|
||||
if (activeLink) {
|
||||
activeLink.classList.remove("active");
|
||||
}
|
||||
if (newLink) {
|
||||
newLink.classList.add("active");
|
||||
}
|
||||
activeLink = newLink;
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll event handler
|
||||
let ticking = false;
|
||||
function onScroll() {
|
||||
if (!ticking) {
|
||||
requestAnimationFrame(() => {
|
||||
updateActiveLink();
|
||||
ticking = false;
|
||||
});
|
||||
ticking = true;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
|
||||
// Also update on hash change (direct link navigation)
|
||||
window.addEventListener("hashchange", () => {
|
||||
requestAnimationFrame(updateActiveLink);
|
||||
});
|
||||
|
||||
// Set initial active state after a small delay to ensure
|
||||
// browser has completed any hash-based scrolling
|
||||
setTimeout(updateActiveLink, 100);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Apply sidebar state immediately before DOM rendering
|
||||
if (localStorage.getItem("sidebar-collapsed") === "true") {
|
||||
|
|
@ -96,6 +217,13 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
createMobileElements();
|
||||
}
|
||||
|
||||
// Initialize collapsible sidebar sections
|
||||
// after mobile elements are created
|
||||
initCollapsibleSections();
|
||||
|
||||
// Initialize scroll spy for page TOC
|
||||
initScrollSpy();
|
||||
|
||||
// Desktop Sidebar Toggle
|
||||
const sidebarToggle = document.querySelector(".sidebar-toggle");
|
||||
|
||||
|
|
@ -111,8 +239,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
document.body.classList.toggle("sidebar-collapsed");
|
||||
|
||||
// Use documentElement to check state and save to localStorage
|
||||
const isCollapsed =
|
||||
document.documentElement.classList.contains("sidebar-collapsed");
|
||||
const isCollapsed = document.documentElement.classList.contains(
|
||||
"sidebar-collapsed",
|
||||
);
|
||||
localStorage.setItem("sidebar-collapsed", isCollapsed);
|
||||
});
|
||||
}
|
||||
|
|
@ -144,7 +273,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
}
|
||||
|
||||
// Make the entire heading clickable
|
||||
heading.addEventListener("click", function (e) {
|
||||
heading.addEventListener("click", function () {
|
||||
const id = this.id;
|
||||
history.pushState(null, null, "#" + id);
|
||||
|
||||
|
|
@ -257,19 +386,10 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
".mobile-sidebar-container",
|
||||
);
|
||||
const mobileSidebarFab = document.querySelector(".mobile-sidebar-fab");
|
||||
const mobileSidebarContent = document.querySelector(
|
||||
".mobile-sidebar-content",
|
||||
);
|
||||
const mobileSidebarHandle = document.querySelector(".mobile-sidebar-handle");
|
||||
const desktopSidebar = document.querySelector(".sidebar");
|
||||
|
||||
// Always set up FAB if it exists
|
||||
if (mobileSidebarFab && mobileSidebarContainer) {
|
||||
// Populate content if desktop sidebar exists
|
||||
if (desktopSidebar && mobileSidebarContent) {
|
||||
mobileSidebarContent.innerHTML = desktopSidebar.innerHTML;
|
||||
}
|
||||
|
||||
const openMobileSidebar = () => {
|
||||
mobileSidebarContainer.classList.add("active");
|
||||
mobileSidebarFab.setAttribute("aria-expanded", "true");
|
||||
|
|
@ -379,8 +499,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Options filter functionality
|
||||
const optionsFilter = document.getElementById("options-filter");
|
||||
if (optionsFilter) {
|
||||
|
|
@ -404,8 +522,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
);
|
||||
|
||||
// Detect if we're on a mobile device
|
||||
const isMobile =
|
||||
window.innerWidth < 768 || /Mobi|Android/i.test(navigator.userAgent);
|
||||
const isMobile = window.innerWidth < 768 ||
|
||||
/Mobi|Android/i.test(navigator.userAgent);
|
||||
|
||||
// Cache all option elements and their searchable content
|
||||
const options = Array.from(document.querySelectorAll(".option"));
|
||||
|
|
@ -483,7 +601,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
// Update counter at the very end for best performance
|
||||
if (filterResults.visibleCount !== undefined) {
|
||||
if (filterResults.visibleCount < totalCount) {
|
||||
filterResults.textContent = `Showing ${filterResults.visibleCount} of ${totalCount} options`;
|
||||
filterResults.textContent =
|
||||
`Showing ${filterResults.visibleCount} of ${totalCount} options`;
|
||||
filterResults.style.display = "block";
|
||||
} else {
|
||||
filterResults.style.display = "none";
|
||||
|
|
@ -530,8 +649,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
isDescMatch = !isTitleMatch && data.description.includes(term);
|
||||
} else {
|
||||
isTitleMatch = searchTerms.every((term) => data.name.includes(term));
|
||||
isDescMatch =
|
||||
!isTitleMatch &&
|
||||
isDescMatch = !isTitleMatch &&
|
||||
searchTerms.every((term) => data.description.includes(term));
|
||||
}
|
||||
if (isTitleMatch) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue