/** * search.js — Full-text search for sunvpy docs using Fuse.js */ (function () { "use strict"; var fuse = null; var searchIndex = null; var searchOverlay = document.getElementById("searchOverlay"); var searchInput = document.getElementById("searchInput"); var searchResults = document.getElementById("searchResults"); var debounceTimer = null; // Open search function openSearch() { searchOverlay.classList.add("active"); searchInput.value = ""; searchResults.innerHTML = '
输入关键词开始搜索
'; searchInput.focus(); loadIndex(); } // Close search function closeSearch() { searchOverlay.classList.remove("active"); searchInput.blur(); } // Lazy-load search index async function loadIndex() { if (searchIndex) return; try { var resp = await fetch("data/search-index.json"); searchIndex = await resp.json(); fuse = new Fuse(searchIndex, { keys: [ { name: "title", weight: 0.4 }, { name: "headings", weight: 0.3 }, { name: "content", weight: 0.3 }, ], threshold: 0.3, ignoreLocation: true, includeMatches: true, minMatchCharLength: 2, }); } catch (e) { searchResults.innerHTML = '
无法加载搜索索引
'; } } // Perform search function doSearch(query) { if (!fuse || !query.trim()) { searchResults.innerHTML = '
输入关键词开始搜索
'; return; } var results = fuse.search(query, { limit: 20 }); if (results.length === 0) { searchResults.innerHTML = '
未找到匹配结果
'; return; } var html = ""; results.forEach(function (result) { var item = result.item; var context = getContext(result.matches, item); html += '' + '
' + escHtml(item.title) + "
" + '
' + escHtml(item.section) + "
" + '
' + context + "
" + "
"; }); searchResults.innerHTML = html; } // Get matching context snippet function getContext(matches, item) { if (!matches || matches.length === 0) return ""; // Prefer title/content match context for (var i = 0; i < matches.length; i++) { var m = matches[i]; if (m.key === "content" && m.indices && m.indices.length > 0) { var idx = m.indices[0]; var start = Math.max(0, idx[0] - 30); var end = Math.min(item.content.length, idx[1] + 30); var snippet = (start > 0 ? "..." : "") + item.content.substring(start, end) + (end < item.content.length ? "..." : ""); return highlightMatch(snippet, m.value.substring(idx[0], idx[1] + 1)); } if (m.key === "headings" && m.indices && m.indices.length > 0) { var arr = item.headings; for (var h = 0; h < arr.length; h++) { if (arr[h].toLowerCase().indexOf(m.value.toLowerCase()) !== -1) { return highlightMatch(arr[h], m.value); } } } } return ""; } function highlightMatch(text, match) { if (!match) return escHtml(text); var escaped = escHtml(text); var escapedMatch = escHtml(match); return escaped.replace(new RegExp(escapeRegex(escapedMatch), "gi"), "$&"); } function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } // Close search global window.closeSearch = closeSearch; // Event listeners searchInput.addEventListener("input", function () { clearTimeout(debounceTimer); debounceTimer = setTimeout(function () { doSearch(searchInput.value); }, 200); }); document.getElementById("searchClose").addEventListener("click", closeSearch); searchOverlay.addEventListener("click", function (e) { if (e.target === searchOverlay) closeSearch(); }); // Keyboard shortcut: Ctrl+K / Cmd+K document.addEventListener("keydown", function (e) { if ((e.ctrlKey || e.metaKey) && e.key === "k") { e.preventDefault(); openSearch(); } if (e.key === "Escape" && searchOverlay.classList.contains("active")) { closeSearch(); } }); // Sidebar search button document.getElementById("sidebarSearchBtn").addEventListener("click", openSearch); var mobileSearchBtn = document.getElementById("searchBtnMobile"); if (mobileSearchBtn) mobileSearchBtn.addEventListener("click", openSearch); // Navigate on result click searchResults.addEventListener("click", function (e) { var item = e.target.closest(".search-result-item"); if (item) { closeSearch(); } }); function escHtml(s) { if (!s) return ""; return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function escAttr(s) { return escHtml(s); } })();