mirror of
https://github.com/NotAShelf/neovim-flake.git
synced 2026-02-03 14:30:28 +01:00
Deploy PR #1288 preview
This commit is contained in:
parent
0243effe52
commit
fa1b7bf7a2
13 changed files with 55209 additions and 0 deletions
740
docs-preview-1288/assets/main.js
Normal file
740
docs-preview-1288/assets/main.js
Normal file
|
|
@ -0,0 +1,740 @@
|
|||
// Polyfill for requestIdleCallback for Safari and unsupported browsers
|
||||
if (typeof window.requestIdleCallback === "undefined") {
|
||||
window.requestIdleCallback = function (cb) {
|
||||
const start = Date.now();
|
||||
const idlePeriod = 50;
|
||||
return setTimeout(function () {
|
||||
cb({
|
||||
didTimeout: false,
|
||||
timeRemaining: function () {
|
||||
return Math.max(0, idlePeriod - (Date.now() - start));
|
||||
},
|
||||
});
|
||||
}, 1);
|
||||
};
|
||||
window.cancelIdleCallback = function (id) {
|
||||
clearTimeout(id);
|
||||
};
|
||||
}
|
||||
|
||||
// Create mobile elements if they don't exist
|
||||
function createMobileElements() {
|
||||
// Create mobile sidebar FAB
|
||||
const mobileFab = document.createElement("button");
|
||||
mobileFab.className = "mobile-sidebar-fab";
|
||||
mobileFab.setAttribute("aria-label", "Toggle sidebar menu");
|
||||
mobileFab.innerHTML = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="3" y1="12" x2="21" y2="12"></line>
|
||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
||||
<line x1="3" y1="18" x2="21" y2="18"></line>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
// Only show FAB on mobile (max-width: 800px)
|
||||
function updateFabVisibility() {
|
||||
if (window.innerWidth > 800) {
|
||||
if (mobileFab.parentNode) mobileFab.parentNode.removeChild(mobileFab);
|
||||
} else {
|
||||
if (!document.body.contains(mobileFab)) {
|
||||
document.body.appendChild(mobileFab);
|
||||
}
|
||||
mobileFab.style.display = "flex";
|
||||
}
|
||||
}
|
||||
updateFabVisibility();
|
||||
window.addEventListener("resize", updateFabVisibility);
|
||||
|
||||
// Create mobile sidebar container
|
||||
const mobileContainer = document.createElement("div");
|
||||
mobileContainer.className = "mobile-sidebar-container";
|
||||
mobileContainer.innerHTML = `
|
||||
<div class="mobile-sidebar-handle">
|
||||
<div class="mobile-sidebar-dragger"></div>
|
||||
</div>
|
||||
<div class="mobile-sidebar-content">
|
||||
<!-- Sidebar content will be cloned here -->
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Create mobile search popup
|
||||
const mobileSearchPopup = document.createElement("div");
|
||||
mobileSearchPopup.id = "mobile-search-popup";
|
||||
mobileSearchPopup.className = "mobile-search-popup";
|
||||
mobileSearchPopup.innerHTML = `
|
||||
<div class="mobile-search-container">
|
||||
<div class="mobile-search-header">
|
||||
<input type="text" id="mobile-search-input" placeholder="Search..." />
|
||||
<button id="close-mobile-search" class="close-mobile-search" aria-label="Close search">×</button>
|
||||
</div>
|
||||
<div id="mobile-search-results" class="mobile-search-results"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Insert at end of body so it is not affected by .container flex or stacking context
|
||||
document.body.appendChild(mobileContainer);
|
||||
document.body.appendChild(mobileSearchPopup);
|
||||
|
||||
// Immediately populate mobile sidebar content if desktop sidebar exists
|
||||
const desktopSidebar = document.querySelector(".sidebar");
|
||||
const mobileSidebarContent = mobileContainer.querySelector(
|
||||
".mobile-sidebar-content",
|
||||
);
|
||||
if (desktopSidebar && mobileSidebarContent) {
|
||||
mobileSidebarContent.innerHTML = desktopSidebar.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
// 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") {
|
||||
document.documentElement.classList.add("sidebar-collapsed");
|
||||
document.body.classList.add("sidebar-collapsed");
|
||||
}
|
||||
|
||||
if (!document.querySelector(".mobile-sidebar-fab")) {
|
||||
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");
|
||||
|
||||
// On page load, sync the state from `documentElement` to `body`
|
||||
if (document.documentElement.classList.contains("sidebar-collapsed")) {
|
||||
document.body.classList.add("sidebar-collapsed");
|
||||
}
|
||||
|
||||
if (sidebarToggle) {
|
||||
sidebarToggle.addEventListener("click", function () {
|
||||
// Toggle on both elements for consistency
|
||||
document.documentElement.classList.toggle("sidebar-collapsed");
|
||||
document.body.classList.toggle("sidebar-collapsed");
|
||||
|
||||
// Use documentElement to check state and save to localStorage
|
||||
const isCollapsed = document.documentElement.classList.contains(
|
||||
"sidebar-collapsed",
|
||||
);
|
||||
localStorage.setItem("sidebar-collapsed", isCollapsed);
|
||||
});
|
||||
}
|
||||
|
||||
// Make headings clickable for anchor links
|
||||
const content = document.querySelector(".content");
|
||||
if (content) {
|
||||
const headings = content.querySelectorAll("h1, h2, h3, h4, h5, h6");
|
||||
|
||||
headings.forEach(function (heading) {
|
||||
// Generate a valid, unique ID for each heading
|
||||
if (!heading.id) {
|
||||
let baseId = heading.textContent
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-_]/g, "") // remove invalid chars
|
||||
.replace(/^[^a-z]+/, "") // remove leading non-letters
|
||||
.replace(/[\s-_]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "") // trim leading/trailing dashes
|
||||
.trim();
|
||||
if (!baseId) {
|
||||
baseId = "section";
|
||||
}
|
||||
let id = baseId;
|
||||
let counter = 1;
|
||||
while (document.getElementById(id)) {
|
||||
id = `${baseId}-${counter++}`;
|
||||
}
|
||||
heading.id = id;
|
||||
}
|
||||
|
||||
// Make the entire heading clickable
|
||||
heading.addEventListener("click", function () {
|
||||
const id = this.id;
|
||||
history.pushState(null, null, "#" + id);
|
||||
|
||||
// Scroll with offset
|
||||
const offset = this.getBoundingClientRect().top + window.scrollY - 80;
|
||||
window.scrollTo({
|
||||
top: offset,
|
||||
behavior: "smooth",
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Process footnotes
|
||||
if (content) {
|
||||
const footnoteContainer = document.querySelector(".footnotes-container");
|
||||
|
||||
// Find all footnote references and create a footnotes section
|
||||
const footnoteRefs = content.querySelectorAll('a[href^="#fn"]');
|
||||
if (footnoteRefs.length > 0) {
|
||||
const footnotesDiv = document.createElement("div");
|
||||
footnotesDiv.className = "footnotes";
|
||||
|
||||
const footnotesHeading = document.createElement("h2");
|
||||
footnotesHeading.textContent = "Footnotes";
|
||||
footnotesDiv.appendChild(footnotesHeading);
|
||||
|
||||
const footnotesList = document.createElement("ol");
|
||||
footnoteContainer.appendChild(footnotesDiv);
|
||||
footnotesDiv.appendChild(footnotesList);
|
||||
|
||||
// Add footnotes
|
||||
document.querySelectorAll(".footnote").forEach((footnote) => {
|
||||
const id = footnote.id;
|
||||
const content = footnote.innerHTML;
|
||||
|
||||
const li = document.createElement("li");
|
||||
li.id = id;
|
||||
li.innerHTML = content;
|
||||
|
||||
// Add backlink
|
||||
const backlink = document.createElement("a");
|
||||
backlink.href = "#fnref:" + id.replace("fn:", "");
|
||||
backlink.className = "footnote-backlink";
|
||||
backlink.textContent = "↩";
|
||||
li.appendChild(backlink);
|
||||
|
||||
footnotesList.appendChild(li);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Copy link functionality
|
||||
document.querySelectorAll(".copy-link").forEach(function (copyLink) {
|
||||
copyLink.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Get option ID from parent element
|
||||
const option = copyLink.closest(".option");
|
||||
const optionId = option.id;
|
||||
|
||||
// Create URL with hash
|
||||
const url = new URL(window.location.href);
|
||||
url.hash = optionId;
|
||||
|
||||
// Copy to clipboard
|
||||
navigator.clipboard
|
||||
.writeText(url.toString())
|
||||
.then(function () {
|
||||
// Show feedback
|
||||
const feedback = copyLink.nextElementSibling;
|
||||
feedback.style.display = "inline";
|
||||
|
||||
// Hide after 2 seconds
|
||||
setTimeout(function () {
|
||||
feedback.style.display = "none";
|
||||
}, 2000);
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error("Could not copy link: ", err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Handle initial hash navigation
|
||||
function scrollToElement(element) {
|
||||
if (element) {
|
||||
const offset = element.getBoundingClientRect().top + window.scrollY - 80;
|
||||
window.scrollTo({
|
||||
top: offset,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (window.location.hash) {
|
||||
const targetElement = document.querySelector(window.location.hash);
|
||||
if (targetElement) {
|
||||
setTimeout(() => scrollToElement(targetElement), 0);
|
||||
// Add highlight class for options page
|
||||
if (targetElement.classList.contains("option")) {
|
||||
targetElement.classList.add("highlight");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile Sidebar Functionality
|
||||
const mobileSidebarContainer = document.querySelector(
|
||||
".mobile-sidebar-container",
|
||||
);
|
||||
const mobileSidebarFab = document.querySelector(".mobile-sidebar-fab");
|
||||
const mobileSidebarHandle = document.querySelector(".mobile-sidebar-handle");
|
||||
|
||||
// Always set up FAB if it exists
|
||||
if (mobileSidebarFab && mobileSidebarContainer) {
|
||||
const openMobileSidebar = () => {
|
||||
mobileSidebarContainer.classList.add("active");
|
||||
mobileSidebarFab.setAttribute("aria-expanded", "true");
|
||||
mobileSidebarContainer.setAttribute("aria-hidden", "false");
|
||||
mobileSidebarFab.classList.add("fab-hidden"); // hide FAB when drawer is open
|
||||
};
|
||||
|
||||
const closeMobileSidebar = () => {
|
||||
mobileSidebarContainer.classList.remove("active");
|
||||
mobileSidebarFab.setAttribute("aria-expanded", "false");
|
||||
mobileSidebarContainer.setAttribute("aria-hidden", "true");
|
||||
mobileSidebarFab.classList.remove("fab-hidden"); // Show FAB when drawer is closed
|
||||
};
|
||||
|
||||
mobileSidebarFab.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
if (mobileSidebarContainer.classList.contains("active")) {
|
||||
closeMobileSidebar();
|
||||
} else {
|
||||
openMobileSidebar();
|
||||
}
|
||||
});
|
||||
|
||||
// Only set up drag functionality if handle exists
|
||||
if (mobileSidebarHandle) {
|
||||
// Drag functionality
|
||||
let isDragging = false;
|
||||
let startY = 0;
|
||||
let startHeight = 0;
|
||||
|
||||
// Cleanup function for drag interruption
|
||||
function cleanupDrag() {
|
||||
if (isDragging) {
|
||||
isDragging = false;
|
||||
mobileSidebarHandle.style.cursor = "grab";
|
||||
document.body.style.userSelect = "";
|
||||
}
|
||||
}
|
||||
|
||||
mobileSidebarHandle.addEventListener("mousedown", (e) => {
|
||||
isDragging = true;
|
||||
startY = e.pageY;
|
||||
startHeight = mobileSidebarContainer.offsetHeight;
|
||||
mobileSidebarHandle.style.cursor = "grabbing";
|
||||
document.body.style.userSelect = "none"; // prevent text selection
|
||||
});
|
||||
|
||||
mobileSidebarHandle.addEventListener("touchstart", (e) => {
|
||||
isDragging = true;
|
||||
startY = e.touches[0].pageY;
|
||||
startHeight = mobileSidebarContainer.offsetHeight;
|
||||
});
|
||||
|
||||
document.addEventListener("mousemove", (e) => {
|
||||
if (!isDragging) return;
|
||||
const deltaY = startY - e.pageY;
|
||||
const newHeight = startHeight + deltaY;
|
||||
const vh = window.innerHeight;
|
||||
const minHeight = vh * 0.15;
|
||||
const maxHeight = vh * 0.9;
|
||||
|
||||
if (newHeight >= minHeight && newHeight <= maxHeight) {
|
||||
mobileSidebarContainer.style.height = `${newHeight}px`;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("touchmove", (e) => {
|
||||
if (!isDragging) return;
|
||||
const deltaY = startY - e.touches[0].pageY;
|
||||
const newHeight = startHeight + deltaY;
|
||||
const vh = window.innerHeight;
|
||||
const minHeight = vh * 0.15;
|
||||
const maxHeight = vh * 0.9;
|
||||
|
||||
if (newHeight >= minHeight && newHeight <= maxHeight) {
|
||||
mobileSidebarContainer.style.height = `${newHeight}px`;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("mouseup", cleanupDrag);
|
||||
document.addEventListener("touchend", cleanupDrag);
|
||||
window.addEventListener("blur", cleanupDrag);
|
||||
document.addEventListener("visibilitychange", function () {
|
||||
if (document.hidden) cleanupDrag();
|
||||
});
|
||||
}
|
||||
|
||||
// Close on outside click
|
||||
document.addEventListener("click", (event) => {
|
||||
if (
|
||||
mobileSidebarContainer.classList.contains("active") &&
|
||||
!mobileSidebarContainer.contains(event.target) &&
|
||||
!mobileSidebarFab.contains(event.target)
|
||||
) {
|
||||
closeMobileSidebar();
|
||||
}
|
||||
});
|
||||
|
||||
// Close on escape key
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (
|
||||
event.key === "Escape" &&
|
||||
mobileSidebarContainer.classList.contains("active")
|
||||
) {
|
||||
closeMobileSidebar();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Options filter functionality
|
||||
const optionsFilter = document.getElementById("options-filter");
|
||||
if (optionsFilter) {
|
||||
const optionsContainer = document.querySelector(".options-container");
|
||||
if (!optionsContainer) return;
|
||||
|
||||
// Only inject the style if it doesn't already exist
|
||||
if (!document.head.querySelector("style[data-options-hidden]")) {
|
||||
const styleEl = document.createElement("style");
|
||||
styleEl.setAttribute("data-options-hidden", "");
|
||||
styleEl.textContent = ".option-hidden{display:none!important}";
|
||||
document.head.appendChild(styleEl);
|
||||
}
|
||||
|
||||
// Create filter results counter
|
||||
const filterResults = document.createElement("div");
|
||||
filterResults.className = "filter-results";
|
||||
optionsFilter.parentNode.insertBefore(
|
||||
filterResults,
|
||||
optionsFilter.nextSibling,
|
||||
);
|
||||
|
||||
// Detect if we're on a mobile device
|
||||
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"));
|
||||
const totalCount = options.length;
|
||||
|
||||
// Store the original order of option elements
|
||||
const originalOptionOrder = options.slice();
|
||||
|
||||
// Pre-process and optimize searchable content
|
||||
const optionsData = options.map((option) => {
|
||||
const nameElem = option.querySelector(".option-name");
|
||||
const descriptionElem = option.querySelector(".option-description");
|
||||
const id = option.id ? option.id.toLowerCase() : "";
|
||||
const name = nameElem ? nameElem.textContent.toLowerCase() : "";
|
||||
const description = descriptionElem
|
||||
? descriptionElem.textContent.toLowerCase()
|
||||
: "";
|
||||
|
||||
// Extract keywords for faster searching
|
||||
const keywords = (id + " " + name + " " + description)
|
||||
.toLowerCase()
|
||||
.split(/\s+/)
|
||||
.filter((word) => word.length > 1);
|
||||
|
||||
return {
|
||||
element: option,
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
keywords,
|
||||
searchText: (id + " " + name + " " + description).toLowerCase(),
|
||||
};
|
||||
});
|
||||
|
||||
// Chunk size and rendering variables
|
||||
const CHUNK_SIZE = isMobile ? 15 : 40;
|
||||
let pendingRender = null;
|
||||
let currentChunk = 0;
|
||||
let itemsToProcess = [];
|
||||
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function () {
|
||||
const context = this;
|
||||
const args = arguments;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func.apply(context, args), wait);
|
||||
};
|
||||
}
|
||||
|
||||
// Process options in chunks to prevent UI freezing
|
||||
function processNextChunk() {
|
||||
const startIdx = currentChunk * CHUNK_SIZE;
|
||||
const endIdx = Math.min(startIdx + CHUNK_SIZE, itemsToProcess.length);
|
||||
|
||||
if (startIdx < itemsToProcess.length) {
|
||||
// Process current chunk
|
||||
for (let i = startIdx; i < endIdx; i++) {
|
||||
const item = itemsToProcess[i];
|
||||
if (item.visible) {
|
||||
item.element.classList.remove("option-hidden");
|
||||
} else {
|
||||
item.element.classList.add("option-hidden");
|
||||
}
|
||||
}
|
||||
|
||||
currentChunk++;
|
||||
pendingRender = requestAnimationFrame(processNextChunk);
|
||||
} else {
|
||||
// Finished processing all chunks
|
||||
pendingRender = null;
|
||||
currentChunk = 0;
|
||||
itemsToProcess = [];
|
||||
|
||||
// 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.style.display = "block";
|
||||
} else {
|
||||
filterResults.style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function filterOptions() {
|
||||
const searchTerm = optionsFilter.value.toLowerCase().trim();
|
||||
|
||||
if (pendingRender) {
|
||||
cancelAnimationFrame(pendingRender);
|
||||
pendingRender = null;
|
||||
}
|
||||
currentChunk = 0;
|
||||
itemsToProcess = [];
|
||||
|
||||
if (searchTerm === "") {
|
||||
// Restore original DOM order when filter is cleared
|
||||
const fragment = document.createDocumentFragment();
|
||||
originalOptionOrder.forEach((option) => {
|
||||
option.classList.remove("option-hidden");
|
||||
fragment.appendChild(option);
|
||||
});
|
||||
optionsContainer.appendChild(fragment);
|
||||
filterResults.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
const searchTerms = searchTerm
|
||||
.split(/\s+/)
|
||||
.filter((term) => term.length > 0);
|
||||
let visibleCount = 0;
|
||||
|
||||
const titleMatches = [];
|
||||
const descMatches = [];
|
||||
optionsData.forEach((data) => {
|
||||
let isTitleMatch = false;
|
||||
let isDescMatch = false;
|
||||
if (searchTerms.length === 1) {
|
||||
const term = searchTerms[0];
|
||||
isTitleMatch = data.name.includes(term);
|
||||
isDescMatch = !isTitleMatch && data.description.includes(term);
|
||||
} else {
|
||||
isTitleMatch = searchTerms.every((term) => data.name.includes(term));
|
||||
isDescMatch = !isTitleMatch &&
|
||||
searchTerms.every((term) => data.description.includes(term));
|
||||
}
|
||||
if (isTitleMatch) {
|
||||
titleMatches.push(data);
|
||||
} else if (isDescMatch) {
|
||||
descMatches.push(data);
|
||||
}
|
||||
});
|
||||
|
||||
if (searchTerms.length === 1) {
|
||||
const term = searchTerms[0];
|
||||
titleMatches.sort(
|
||||
(a, b) => a.name.indexOf(term) - b.name.indexOf(term),
|
||||
);
|
||||
descMatches.sort(
|
||||
(a, b) => a.description.indexOf(term) - b.description.indexOf(term),
|
||||
);
|
||||
}
|
||||
|
||||
itemsToProcess = [];
|
||||
titleMatches.forEach((data) => {
|
||||
visibleCount++;
|
||||
itemsToProcess.push({ element: data.element, visible: true });
|
||||
});
|
||||
descMatches.forEach((data) => {
|
||||
visibleCount++;
|
||||
itemsToProcess.push({ element: data.element, visible: true });
|
||||
});
|
||||
optionsData.forEach((data) => {
|
||||
if (!itemsToProcess.some((item) => item.element === data.element)) {
|
||||
itemsToProcess.push({ element: data.element, visible: false });
|
||||
}
|
||||
});
|
||||
|
||||
// Reorder DOM so all title matches, then desc matches, then hidden
|
||||
const fragment = document.createDocumentFragment();
|
||||
itemsToProcess.forEach((item) => {
|
||||
fragment.appendChild(item.element);
|
||||
});
|
||||
optionsContainer.appendChild(fragment);
|
||||
|
||||
filterResults.visibleCount = visibleCount;
|
||||
pendingRender = requestAnimationFrame(processNextChunk);
|
||||
}
|
||||
|
||||
// Use different debounce times for desktop vs mobile
|
||||
const debouncedFilter = debounce(filterOptions, isMobile ? 200 : 100);
|
||||
|
||||
// Set up event listeners
|
||||
optionsFilter.addEventListener("input", debouncedFilter);
|
||||
optionsFilter.addEventListener("change", filterOptions);
|
||||
|
||||
// Allow clearing with Escape key
|
||||
optionsFilter.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Escape") {
|
||||
optionsFilter.value = "";
|
||||
filterOptions();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle visibility changes
|
||||
document.addEventListener("visibilitychange", function () {
|
||||
if (!document.hidden && optionsFilter.value) {
|
||||
filterOptions();
|
||||
}
|
||||
});
|
||||
|
||||
// Initially trigger filter if there's a value
|
||||
if (optionsFilter.value) {
|
||||
filterOptions();
|
||||
}
|
||||
|
||||
// Pre-calculate heights for smoother scrolling
|
||||
if (isMobile && totalCount > 50) {
|
||||
requestIdleCallback(() => {
|
||||
const sampleOption = options[0];
|
||||
if (sampleOption) {
|
||||
const height = sampleOption.offsetHeight;
|
||||
if (height > 0) {
|
||||
options.forEach((opt) => {
|
||||
opt.style.containIntrinsicSize = `0 ${height}px`;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
1
docs-preview-1288/assets/search-data.json
Normal file
1
docs-preview-1288/assets/search-data.json
Normal file
File diff suppressed because one or more lines are too long
298
docs-preview-1288/assets/search-worker.js
Normal file
298
docs-preview-1288/assets/search-worker.js
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
const isWordBoundary = (char) =>
|
||||
/[A-Z]/.test(char) || /[-_\/.]/.test(char) || /\s/.test(char);
|
||||
|
||||
const isCaseTransition = (prev, curr) => {
|
||||
const prevIsUpper = prev.toLowerCase() !== prev;
|
||||
const currIsUpper = curr.toLowerCase() !== curr;
|
||||
return (
|
||||
prevIsUpper && currIsUpper && prev.toLowerCase() !== curr.toLowerCase()
|
||||
);
|
||||
};
|
||||
|
||||
const findBestSubsequenceMatch = (query, target) => {
|
||||
const n = query.length;
|
||||
const m = target.length;
|
||||
|
||||
if (n === 0 || m === 0) return null;
|
||||
|
||||
const positions = [];
|
||||
|
||||
const memo = new Map();
|
||||
const key = (qIdx, tIdx, gap) => `${qIdx}:${tIdx}:${gap}`;
|
||||
|
||||
const findBest = (qIdx, tIdx, currentGap) => {
|
||||
if (qIdx === n) {
|
||||
return { done: true, positions: [...positions], gap: currentGap };
|
||||
}
|
||||
|
||||
const memoKey = key(qIdx, tIdx, currentGap);
|
||||
if (memo.has(memoKey)) {
|
||||
return memo.get(memoKey);
|
||||
}
|
||||
|
||||
let bestResult = null;
|
||||
|
||||
for (let i = tIdx; i < m; i++) {
|
||||
if (target[i] === query[qIdx]) {
|
||||
positions.push(i);
|
||||
const gap = qIdx === 0 ? 0 : i - positions[positions.length - 2] - 1;
|
||||
const newGap = currentGap + gap;
|
||||
|
||||
if (newGap > m) {
|
||||
positions.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
const result = findBest(qIdx + 1, i + 1, newGap);
|
||||
positions.pop();
|
||||
|
||||
if (result && (!bestResult || result.gap < bestResult.gap)) {
|
||||
bestResult = result;
|
||||
if (result.gap === 0) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memo.set(memoKey, bestResult);
|
||||
return bestResult;
|
||||
};
|
||||
|
||||
const result = findBest(0, 0, 0);
|
||||
if (!result) return null;
|
||||
|
||||
const consecutive = (() => {
|
||||
let c = 1;
|
||||
for (let i = 1; i < result.positions.length; i++) {
|
||||
if (result.positions[i] === result.positions[i - 1] + 1) {
|
||||
c++;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
})();
|
||||
|
||||
return {
|
||||
positions: result.positions,
|
||||
consecutive,
|
||||
score: calculateMatchScore(query, target, result.positions, consecutive),
|
||||
};
|
||||
};
|
||||
|
||||
const calculateMatchScore = (query, target, positions, consecutive) => {
|
||||
const n = positions.length;
|
||||
const m = target.length;
|
||||
|
||||
if (n === 0) return 0;
|
||||
|
||||
let score = 1.0;
|
||||
|
||||
const startBonus = (m - positions[0]) / m;
|
||||
score += startBonus * 0.5;
|
||||
|
||||
let gapPenalty = 0;
|
||||
for (let i = 1; i < n; i++) {
|
||||
const gap = positions[i] - positions[i - 1] - 1;
|
||||
if (gap > 0) {
|
||||
gapPenalty += Math.min(gap / m, 1.0) * 0.3;
|
||||
}
|
||||
}
|
||||
score -= gapPenalty;
|
||||
|
||||
const consecutiveBonus = consecutive / n;
|
||||
score += consecutiveBonus * 0.3;
|
||||
|
||||
let boundaryBonus = 0;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const char = target[positions[i]];
|
||||
if (i === 0 || isWordBoundary(char)) {
|
||||
boundaryBonus += 0.05;
|
||||
}
|
||||
if (i > 0) {
|
||||
const prevChar = target[positions[i - 1]];
|
||||
if (isCaseTransition(prevChar, char)) {
|
||||
boundaryBonus += 0.03;
|
||||
}
|
||||
}
|
||||
}
|
||||
score = Math.min(1.0, score + boundaryBonus);
|
||||
|
||||
const lengthPenalty = Math.abs(query.length - n) / Math.max(query.length, m);
|
||||
score -= lengthPenalty * 0.2;
|
||||
|
||||
return Math.max(0, Math.min(1.0, score));
|
||||
};
|
||||
|
||||
const fuzzyMatch = (query, target) => {
|
||||
const lowerQuery = query.toLowerCase();
|
||||
const lowerTarget = target.toLowerCase();
|
||||
|
||||
if (lowerQuery.length === 0) return null;
|
||||
if (lowerTarget.length === 0) return null;
|
||||
|
||||
if (lowerTarget === lowerQuery) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
if (lowerTarget.includes(lowerQuery)) {
|
||||
const ratio = lowerQuery.length / lowerTarget.length;
|
||||
return 0.8 + ratio * 0.2;
|
||||
}
|
||||
|
||||
const match = findBestSubsequenceMatch(lowerQuery, lowerTarget);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Math.min(1.0, match.score);
|
||||
};
|
||||
|
||||
self.onmessage = function (e) {
|
||||
const { messageId, type, data } = e.data;
|
||||
|
||||
const respond = (type, data) => {
|
||||
self.postMessage({ messageId, type, data });
|
||||
};
|
||||
|
||||
const respondError = (error) => {
|
||||
self.postMessage({
|
||||
messageId,
|
||||
type: "error",
|
||||
error: error.message || String(error),
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
if (type === "tokenize") {
|
||||
const text = typeof data === "string" ? data : "";
|
||||
const words = text.toLowerCase().match(/\b[a-zA-Z0-9_-]+\b/g) || [];
|
||||
const tokens = words.filter((word) => word.length > 2);
|
||||
const uniqueTokens = Array.from(new Set(tokens));
|
||||
respond("tokens", uniqueTokens);
|
||||
} else if (type === "search") {
|
||||
const { query, limit = 10 } = data;
|
||||
|
||||
if (!query || typeof query !== "string") {
|
||||
respond("results", []);
|
||||
return;
|
||||
}
|
||||
|
||||
const rawQuery = query.toLowerCase();
|
||||
const text = typeof query === "string" ? query : "";
|
||||
const words = text.toLowerCase().match(/\b[a-zA-Z0-9_-]+\b/g) || [];
|
||||
const searchTerms = words.filter((word) => word.length > 2);
|
||||
|
||||
let documents = [];
|
||||
if (typeof data.documents === "string") {
|
||||
documents = JSON.parse(data.documents);
|
||||
} else if (Array.isArray(data.documents)) {
|
||||
documents = data.documents;
|
||||
} else if (typeof data.transferables === "string") {
|
||||
documents = JSON.parse(data.transferables);
|
||||
}
|
||||
|
||||
if (!Array.isArray(documents) || documents.length === 0) {
|
||||
respond("results", []);
|
||||
return;
|
||||
}
|
||||
|
||||
const useFuzzySearch = rawQuery.length >= 3;
|
||||
|
||||
if (searchTerms.length === 0 && rawQuery.length < 3) {
|
||||
respond("results", []);
|
||||
return;
|
||||
}
|
||||
|
||||
const pageMatches = new Map();
|
||||
|
||||
// Pre-compute lower-case strings for each document
|
||||
const processedDocs = documents.map((doc, docId) => {
|
||||
const title = typeof doc.title === "string" ? doc.title : "";
|
||||
const content = typeof doc.content === "string" ? doc.content : "";
|
||||
|
||||
return {
|
||||
docId,
|
||||
doc,
|
||||
lowerTitle: title.toLowerCase(),
|
||||
lowerContent: content.toLowerCase(),
|
||||
};
|
||||
});
|
||||
|
||||
// First pass: Score pages with fuzzy matching
|
||||
processedDocs.forEach(({ docId, doc, lowerTitle, lowerContent }) => {
|
||||
let match = pageMatches.get(docId);
|
||||
if (!match) {
|
||||
match = { doc, pageScore: 0, matchingAnchors: [] };
|
||||
pageMatches.set(docId, match);
|
||||
}
|
||||
|
||||
if (useFuzzySearch) {
|
||||
const fuzzyTitleScore = fuzzyMatch(rawQuery, lowerTitle);
|
||||
if (fuzzyTitleScore !== null) {
|
||||
match.pageScore += fuzzyTitleScore * 100;
|
||||
}
|
||||
|
||||
const fuzzyContentScore = fuzzyMatch(rawQuery, lowerContent);
|
||||
if (fuzzyContentScore !== null) {
|
||||
match.pageScore += fuzzyContentScore * 30;
|
||||
}
|
||||
}
|
||||
|
||||
// Token-based exact matching
|
||||
searchTerms.forEach((term) => {
|
||||
if (lowerTitle.includes(term)) {
|
||||
match.pageScore += lowerTitle === term ? 20 : 10;
|
||||
}
|
||||
if (lowerContent.includes(term)) {
|
||||
match.pageScore += 2;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Second pass: Find matching anchors
|
||||
pageMatches.forEach((match) => {
|
||||
const doc = match.doc;
|
||||
if (
|
||||
!doc.anchors ||
|
||||
!Array.isArray(doc.anchors) ||
|
||||
doc.anchors.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
doc.anchors.forEach((anchor) => {
|
||||
if (!anchor || !anchor.text) return;
|
||||
|
||||
const anchorText = anchor.text.toLowerCase();
|
||||
let anchorMatches = false;
|
||||
|
||||
if (useFuzzySearch) {
|
||||
const fuzzyScore = fuzzyMatch(rawQuery, anchorText);
|
||||
if (fuzzyScore !== null && fuzzyScore >= 0.4) {
|
||||
anchorMatches = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!anchorMatches) {
|
||||
searchTerms.forEach((term) => {
|
||||
if (anchorText.includes(term)) {
|
||||
anchorMatches = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (anchorMatches) {
|
||||
match.matchingAnchors.push(anchor);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const results = Array.from(pageMatches.values())
|
||||
.filter((m) => m.pageScore > 5)
|
||||
.sort((a, b) => b.pageScore - a.pageScore)
|
||||
.slice(0, limit);
|
||||
|
||||
respond("results", results);
|
||||
}
|
||||
} catch (error) {
|
||||
respondError(error);
|
||||
}
|
||||
};
|
||||
1439
docs-preview-1288/assets/search.js
Normal file
1439
docs-preview-1288/assets/search.js
Normal file
File diff suppressed because it is too large
Load diff
2143
docs-preview-1288/assets/style.css
Normal file
2143
docs-preview-1288/assets/style.css
Normal file
File diff suppressed because it is too large
Load diff
663
docs-preview-1288/configuring.html
Normal file
663
docs-preview-1288/configuring.html
Normal file
File diff suppressed because one or more lines are too long
535
docs-preview-1288/hacking.html
Normal file
535
docs-preview-1288/hacking.html
Normal file
File diff suppressed because one or more lines are too long
395
docs-preview-1288/index.html
Normal file
395
docs-preview-1288/index.html
Normal file
File diff suppressed because one or more lines are too long
46437
docs-preview-1288/options.html
Normal file
46437
docs-preview-1288/options.html
Normal file
File diff suppressed because it is too large
Load diff
153
docs-preview-1288/quirks.html
Normal file
153
docs-preview-1288/quirks.html
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Known Issues and Quirks</title>
|
||||
|
||||
<script>
|
||||
// Apply sidebar state immediately to prevent flash
|
||||
(function () {
|
||||
if (localStorage.getItem("sidebar-collapsed") === "true") {
|
||||
document.documentElement.classList.add("sidebar-collapsed");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="assets/style.css" />
|
||||
<script defer src="assets/main.js"></script>
|
||||
|
||||
<script>
|
||||
window.searchNamespace = window.searchNamespace || {};
|
||||
window.searchNamespace.rootPath = "";
|
||||
</script>
|
||||
<script defer src="assets/search.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="header-left">
|
||||
<h1 class="site-title">
|
||||
<a href="index.html">NVF</a>
|
||||
</h1>
|
||||
|
||||
<nav class="header-nav">
|
||||
<ul>
|
||||
<li >
|
||||
<a href="options.html">Options</a>
|
||||
</li>
|
||||
|
||||
<li><a href="search.html">Search</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="search-container">
|
||||
<input type="text" id="search-input" placeholder="Search..." />
|
||||
<div id="search-results" class="search-results"></div>
|
||||
</div>
|
||||
|
||||
</header>
|
||||
|
||||
<div class="layout">
|
||||
<div class="sidebar-toggle" aria-label="Toggle sidebar">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
>
|
||||
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<nav class="sidebar">
|
||||
<details class="sidebar-section" data-section="docs" open>
|
||||
<summary>Documents</summary>
|
||||
<div class="sidebar-section-content">
|
||||
<ul>
|
||||
<li><a href="index.html">Introduction</a></li>
|
||||
<li><a href="configuring.html">Configuring nvf</a></li>
|
||||
<li><a href="hacking.html">Hacking nvf</a></li>
|
||||
<li><a href="tips.html">Helpful Tips</a></li>
|
||||
<li><a href="quirks.html">Known Issues and Quirks</a></li>
|
||||
<li><a href="release-notes.html">Release Notes</a></li>
|
||||
<li><a href="search.html">Search</a></li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="sidebar-section" data-section="toc" open>
|
||||
<summary>Contents</summary>
|
||||
<div class="sidebar-section-content">
|
||||
<ul class="toc-list">
|
||||
<li><a href="#ch-known-issues-quirks">Known Issues and Quirks</a>
|
||||
<ul><li><a href="#ch-quirks-nodejs">NodeJS</a>
|
||||
<ul><li><a href="#sec-eslint-plugin-prettier">eslint-plugin-prettier</a>
|
||||
</ul><li><a href="#ch-bugs-suggestions">Bugs & Suggestions</a>
|
||||
</li></ul></li>
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
</nav>
|
||||
|
||||
<main class="content"><html><head></head><body><h1 id="ch-known-issues-quirks">Known Issues and Quirks</h1>
|
||||
<p>At times, certain plugins and modules may refuse to play nicely with your setup,
|
||||
be it a result of generating Lua from Nix, or the state of packaging. This page,
|
||||
in turn, will list any known modules or plugins that are known to misbehave, and
|
||||
possible workarounds that you may apply.</p>
|
||||
<h2 id="ch-quirks-nodejs">NodeJS</h2>
|
||||
<h3 id="sec-eslint-plugin-prettier">eslint-plugin-prettier</h3>
|
||||
<p>When working with NodeJS, which is <em>obviously</em> known for its meticulous
|
||||
standards, most things are bound to work as expected but some projects, tools
|
||||
and settings may fool the default configurations of tools provided by <strong>nvf</strong>.</p>
|
||||
<p>If</p>
|
||||
<p>If <a href="https://github.com/prettier/eslint-plugin-prettier">eslint-plugin-prettier</a> or similar is included, you might get a situation
|
||||
where your Eslint configuration diagnoses your formatting according to its own
|
||||
config (usually <code>.eslintrc.js</code>). The issue there is your formatting is made via
|
||||
prettierd.</p>
|
||||
<p>This results in auto-formatting relying on your prettier configuration, while
|
||||
your Eslint configuration diagnoses formatting "issues" while it's
|
||||
<a href="https://prettier.io/docs/en/comparison.html">not supposed to</a>. In the end, you get discrepancies between what your editor
|
||||
does and what it wants.</p>
|
||||
<p>Solutions are:</p>
|
||||
<ol>
|
||||
<li>Don't add a formatting config to Eslint, instead separate Prettier and
|
||||
Eslint.</li>
|
||||
<li>PR the repo in question to add an ESLint formatter, and configure <strong>nvf</strong> to
|
||||
use it.</li>
|
||||
</ol>
|
||||
<h2 id="ch-bugs-suggestions">Bugs & Suggestions</h2>
|
||||
<p>Some quirks are not exactly quirks, but bugs in the module system. If you notice
|
||||
any issues with <strong>nvf</strong>, or this documentation, then please consider reporting
|
||||
them over at the <a href="https://github.com/notashelf/nvf/issues">issue tracker</a>. Issues tab, in addition to the
|
||||
<a href="https://github.com/notashelf/nvf/discussions">discussions tab</a> is a good place as any to request new features.</p>
|
||||
<p>You may also consider submitting bug fixes, feature additions and upstreamed
|
||||
changes that you think are critical over at the <a href="https://github.com/notashelf/nvf/pulls">pull requests tab</a>.</p>
|
||||
</body></html></main>
|
||||
</div>
|
||||
|
||||
<aside class="page-toc">
|
||||
<nav class="page-toc-nav">
|
||||
<h3>On this page</h3>
|
||||
<ul class="page-toc-list">
|
||||
<li><a href="#ch-known-issues-quirks">Known Issues and Quirks</a>
|
||||
<ul><li><a href="#ch-quirks-nodejs">NodeJS</a>
|
||||
<ul><li><a href="#sec-eslint-plugin-prettier">eslint-plugin-prettier</a>
|
||||
</ul><li><a href="#ch-bugs-suggestions">Bugs & Suggestions</a>
|
||||
</li></ul></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<footer>
|
||||
<p>Generated with ndg</p>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
2058
docs-preview-1288/release-notes.html
Normal file
2058
docs-preview-1288/release-notes.html
Normal file
File diff suppressed because one or more lines are too long
110
docs-preview-1288/search.html
Normal file
110
docs-preview-1288/search.html
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>NVF - Search</title>
|
||||
|
||||
<script>
|
||||
// Apply sidebar state immediately to prevent flash
|
||||
(function () {
|
||||
if (localStorage.getItem("sidebar-collapsed") === "true") {
|
||||
document.documentElement.classList.add("sidebar-collapsed");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="assets/style.css" />
|
||||
<script defer src="assets/main.js"></script>
|
||||
<script>
|
||||
window.searchNamespace = window.searchNamespace || {};
|
||||
window.searchNamespace.rootPath = "";
|
||||
</script>
|
||||
<script defer src="assets/search.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="header-left">
|
||||
<h1 class="site-title">
|
||||
<a href="index.html">NVF</a>
|
||||
</h1>
|
||||
</div>
|
||||
<nav class="header-nav">
|
||||
<ul>
|
||||
<li >
|
||||
<a href="options.html">Options</a>
|
||||
</li>
|
||||
|
||||
<li><a href="search.html">Search</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="search-container">
|
||||
<input type="text" id="search-input" placeholder="Search..." />
|
||||
<div id="search-results" class="search-results"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="layout">
|
||||
<div class="sidebar-toggle" aria-label="Toggle sidebar">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
>
|
||||
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<nav id="sidebar" class="sidebar">
|
||||
<div class="docs-nav">
|
||||
<h2>Documents</h2>
|
||||
<ul>
|
||||
<li><a href="index.html">Introduction</a></li>
|
||||
<li><a href="configuring.html">Configuring nvf</a></li>
|
||||
<li><a href="hacking.html">Hacking nvf</a></li>
|
||||
<li><a href="tips.html">Helpful Tips</a></li>
|
||||
<li><a href="quirks.html">Known Issues and Quirks</a></li>
|
||||
<li><a href="release-notes.html">Release Notes</a></li>
|
||||
<li><a href="search.html">Search</a></li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="toc">
|
||||
<h2>Contents</h2>
|
||||
<ul class="toc-list">
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="content">
|
||||
<h1>Search</h1>
|
||||
<div class="search-page">
|
||||
<div class="search-form">
|
||||
<input
|
||||
type="text"
|
||||
id="search-page-input"
|
||||
placeholder="Search..."
|
||||
autofocus
|
||||
/>
|
||||
</div>
|
||||
<div id="search-page-results" class="search-page-results"></div>
|
||||
</div>
|
||||
<div class="footnotes-container">
|
||||
<!-- Footnotes will be appended here -->
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>Generated with ndg</p>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
237
docs-preview-1288/tips.html
Normal file
237
docs-preview-1288/tips.html
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue