let lunrIndex; let documents = []; /* ------------------------------ JSON → documents -------------------------------- */ function extractDocuments(node, currentPath = "") { if (node.type === "folder" && node.children) { const nextPath = currentPath ? `${currentPath}/${node.name}` : node.name; node.children.forEach(child => { extractDocuments(child, nextPath); }); } if (node.type === "file") { const filePath = currentPath ? `${currentPath}/${node.name}` : node.name; documents.push({ id: documents.length.toString(), title: node.name, content: node.content || "", path: "/" + filePath.replace(/^output\//, "") }); } } /* ------------------------------ Lunr index -------------------------------- */ function buildIndex() { lunrIndex = lunr(function () { this.ref("id"); this.field("title", { boost: 10 }); this.field("content"); documents.forEach(doc => this.add(doc)); }); console.log("Lunr index built with", documents.length, "documents"); } async function initSearch() { const response = await fetch("test.json"); const json = await response.json(); extractDocuments(json); buildIndex(); } initSearch(); /* ------------------------------ Fuzzy search -------------------------------- */ function buildFuzzyQuery(query) { return query .split(/\s+/) .map(term => `${term}~1`) .join(" "); } function search(query) { if (!lunrIndex) return []; const results = lunrIndex.search(buildFuzzyQuery(query)); return results.map(r => { const doc = documents.find(d => d.id === r.ref); return { title: doc.title, path: doc.path, score: r.score, preview: doc.content.slice(0, 200) + "…" }; }); } /* ------------------------------ DOM elements -------------------------------- */ const searchInput = document.getElementById("search-input"); const searchBtn = document.getElementById("search-btn"); /* ------------------------------ Live dropdown results -------------------------------- */ const resultsBox = document.createElement("div"); resultsBox.id = "search-results"; document.body.appendChild(resultsBox); function showResults(results) { resultsBox.innerHTML = ""; if (results.length === 0) { resultsBox.style.display = "none"; return; } const rect = searchInput.getBoundingClientRect(); resultsBox.style.top = `${rect.bottom + window.scrollY}px`; resultsBox.style.left = `${rect.left + window.scrollX}px`; resultsBox.style.width = `${rect.width}px`; results.slice(0, 5).forEach(result => { const item = document.createElement("div"); item.className = "search-result"; item.innerHTML = `
${result.title}
${result.preview}
`; item.onclick = () => window.location.href = result.path; resultsBox.appendChild(item); }); resultsBox.style.display = "block"; } searchInput.addEventListener("input", () => { const q = searchInput.value.trim(); if (!q) { resultsBox.style.display = "none"; return; } showResults(search(q)); }); /* ------------------------------ Modal popup -------------------------------- */ const modal = document.createElement("div"); modal.id = "search-modal"; modal.innerHTML = `

Search results

`; document.body.appendChild(modal); const modalResults = modal.querySelector("#search-modal-results"); function openSearchModal(results) { modalResults.innerHTML = ""; if (results.length === 0) { modalResults.innerHTML = "

No results found.

"; } results.forEach(result => { const item = document.createElement("div"); item.className = "search-modal-result"; item.innerHTML = `
${result.title}
${result.preview}
`; item.onclick = () => window.location.href = result.path; modalResults.appendChild(item); }); modal.style.display = "block"; } /* ------------------------------ Submit search (Enter / 🔍) -------------------------------- */ function submitSearch() { const q = searchInput.value.trim(); if (!q) return; openSearchModal(search(q)); } searchInput.addEventListener("keydown", e => { if (e.key === "Enter") { e.preventDefault(); submitSearch(); } }); searchBtn.addEventListener("click", submitSearch); /* ------------------------------ Close behaviours -------------------------------- */ document.addEventListener("click", e => { if ( !resultsBox.contains(e.target) && e.target !== searchInput && e.target !== searchBtn ) { resultsBox.style.display = "none"; } }); modal.addEventListener("click", e => { if (e.target.classList.contains("search-modal-backdrop")) { modal.style.display = "none"; } }); document.addEventListener("keydown", e => { if (e.key === "Escape") { modal.style.display = "none"; } });