Deploy PR #1288 preview

This commit is contained in:
GitHub Actions 2026-01-29 19:37:44 +00:00
commit 59c318ec20
13 changed files with 55087 additions and 0 deletions

View 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">&times;</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`;
});
}
}
});
}
}
});

File diff suppressed because one or more lines are too long

View 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);
}
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff