import { api } from "./api.js"; import { state } from "./state.js"; import { $, escapeHtml } from "./utils.js"; import { t, applyI18n, onLangChange } from "./i18n.js"; import { refreshInspector } from "./inspector.js"; let debounceTimer = null; let lastQuery = ""; let resultsCache = null; export function mountGlobalSearch() { const input = $("globalSearchInput"); const panel = $("globalSearchPanel"); applyI18n(input.parentElement); input.addEventListener("input", () => { const q = input.value.trim(); if (debounceTimer) window.clearTimeout(debounceTimer); debounceTimer = window.setTimeout(() => runSearch(q), 200); }); input.addEventListener("focus", () => { if (lastQuery) renderPanel(resultsCache); else renderHint(); panel.hidden = false; }); input.addEventListener("keydown", (event) => { if (event.key === "Escape") { input.value = ""; panel.hidden = true; input.blur(); } if (event.key === "ArrowDown") { event.preventDefault(); const first = panel.querySelector("[data-search-item]"); first?.focus(); } }); document.addEventListener("click", (event) => { if (!panel.contains(event.target) && event.target !== input) panel.hidden = true; }); document.addEventListener("keydown", (event) => { if (event.key === "/" && !event.target.matches("input, textarea, select")) { event.preventDefault(); input.focus(); input.select(); } }); onLangChange(() => { applyI18n(input.parentElement); if (resultsCache) renderPanel(resultsCache); }); } async function runSearch(q) { if (!state.selectedProject) return; lastQuery = q; const panel = $("globalSearchPanel"); if (!q) { renderHint(); return; } try { resultsCache = await api(`/projects/${encodeURIComponent(state.selectedProject)}/search?q=${encodeURIComponent(q)}&limit=8`); renderPanel(resultsCache); } catch (error) { panel.innerHTML = `
${escapeHtml(error.message)}
`; panel.hidden = false; } } function renderHint() { const panel = $("globalSearchPanel"); panel.innerHTML = `
${escapeHtml(t("search.hint"))}
`; panel.hidden = false; } function renderPanel(results) { const panel = $("globalSearchPanel"); if (!results) return; const groups = [ { key: "entities", items: results.entities, render: renderEntity }, { key: "claims", items: results.claims, render: renderClaim }, { key: "pages", items: results.pages, render: renderPage }, { key: "predicates", items: results.predicates, render: renderPredicate }, ].filter((g) => (g.items ?? []).length > 0); if (!groups.length) { panel.innerHTML = `
${escapeHtml(t("search.empty"))}
`; } else { panel.innerHTML = groups.map((g) => `
${escapeHtml(t(`search.group.${g.key}`))}
${g.items.map((item, idx) => g.render(item, idx)).join("")}
`).join(""); panel.querySelectorAll("[data-search-item]").forEach((node) => { node.addEventListener("click", () => handleClick(node.dataset.searchItem, JSON.parse(node.dataset.payload))); node.addEventListener("keydown", (event) => { if (event.key === "Enter") { event.preventDefault(); node.click(); } if (event.key === "ArrowDown") { event.preventDefault(); (node.nextElementSibling || panel.querySelector("[data-search-item]"))?.focus?.(); } if (event.key === "ArrowUp") { event.preventDefault(); (node.previousElementSibling || panel.querySelector("[data-search-item]:last-of-type"))?.focus?.(); } if (event.key === "Escape") { $("globalSearchInput").focus(); } }); }); } panel.hidden = false; } function renderEntity(e) { return ` `; } function renderClaim(c) { const obj = c.object_value ? JSON.stringify(c.object_value) : ""; return ` `; } function renderPage(p) { return ` `; } function renderPredicate(name) { return ` `; } function escapeHtmlAttr(value) { return String(value).replaceAll("&", "&").replaceAll("'", "'").replaceAll('"', """); } function handleClick(kind, payload) { const panel = $("globalSearchPanel"); panel.hidden = true; if (kind === "entity") { activateTab("entities"); state.selection = { kind: "entity", data: { id: payload.id, name: payload.name, type: payload.type, metadata: {} } }; refreshInspector(); } else if (kind === "claim") { activateTab("claims"); state.selection = { kind: "claim", data: { ...payload, object: undefined } }; refreshInspector(); } else if (kind === "page") { if (payload.url) window.open(payload.url, "_blank", "noreferrer"); } else if (kind === "predicate") { activateTab("graph"); const sel = $("graphPredicate"); if (sel) { sel.value = payload; sel.dispatchEvent(new Event("change")); } } } function activateTab(name) { document.querySelectorAll(".tab").forEach((n) => n.classList.toggle("active", n.dataset.tab === name)); document.querySelectorAll(".tab-panel").forEach((n) => n.classList.toggle("active", n.id === name)); }