// SPDX-License-Identifier: GPL-3.0-or-later /* === Command palette (Ctrl/Cmd+K) === One keystroke to jump anywhere: views, actions and findings, filtered as you type. Arrow keys move, Enter runs, Esc closes. Linear's signature, wired to the same functions the toolbar already calls. */ let paletteItems = []; let paletteSel = 0; function isPaletteOpen() { const el = $("#palette"); return !!el && !el.hidden; } /* Every entry is { group, label, hint, run }. Findings come from the report that is already in memory; nothing extra is fetched. */ function paletteCommands() { const cmds = []; for (const v of VIEWS) { const label = v.charAt(0).toUpperCase() + v.slice(1); cmds.push({ group: "Views", label, hint: "Open", run: () => switchView(v) }); } cmds.push({ group: "Actions", label: "Re-run checks", run: () => $("#rerun")?.click() }); cmds.push({ group: "Actions", label: "Toggle theme", run: () => $("#theme")?.click() }); cmds.push({ group: "Actions", label: "Expand all findings", run: () => setAllOpen(true) }); cmds.push({ group: "Actions", label: "Collapse all findings", run: () => setAllOpen(false) }); cmds.push({ group: "Actions", label: "Toggle compact density", run: () => $("#densitybtn")?.click() }); cmds.push({ group: "Actions", label: "Open the all-checks matrix", run: () => $("#checksmatrixbtn")?.click() }); cmds.push({ group: "Actions", label: "Edit thresholds", run: () => $("#threshbtn")?.click() }); cmds.push({ group: "Actions", label: "Export report", run: () => $("#export-trigger")?.click() }); cmds.push({ group: "Actions", label: "Keyboard shortcuts", run: () => openHelp() }); const findings = (typeof lastReportData !== "undefined" && lastReportData && lastReportData.findings) || []; for (const f of findings) { cmds.push({ group: "Findings", label: f.title || f.code || "Finding", hint: (f.code ? f.code + " ยท " : "") + (SEV_NAMES[f.severity] || f.severity || ""), run: () => jumpToFinding(f), }); } return cmds; } function jumpToFinding(f) { switchView("overview"); const el = [...document.querySelectorAll("#report .card")].find((c) => (c.dataset.code || "") === (f.code || "")); if (!el) return; el.setAttribute("open", ""); if (typeof wideModeOn === "function" && wideModeOn() && typeof selectFinding === "function") selectFinding(el); el.scrollIntoView({ block: "center" }); el.focus({ preventScroll: true }); } function paletteFilter(q) { const all = paletteCommands(); const needle = q.trim().toLowerCase(); if (!needle) return all.slice(0, 40); const out = []; for (const c of all) { const hay = (c.label + " " + (c.hint || "") + " " + c.group).toLowerCase(); if (hay.includes(needle)) out.push(c); } return out.slice(0, 40); } function renderPalette(q) { paletteItems = paletteFilter(q); if (paletteSel >= paletteItems.length) paletteSel = 0; const list = $("#palette-list"); if (!list) return; if (!paletteItems.length) { list.innerHTML = '
Nothing matches \u201c' + esc(q.trim()) + "\u201d
"; return; } let html = ""; let last = ""; paletteItems.forEach((c, i) => { if (c.group !== last) { html += '
' + esc(c.group) + "
"; last = c.group; } html += '"; }); list.innerHTML = html; } function movePalette(d) { if (!paletteItems.length) return; paletteSel = (paletteSel + d + paletteItems.length) % paletteItems.length; const list = $("#palette-list"); list.querySelectorAll(".pal-item").forEach((el, i) => el.classList.toggle("on", i === paletteSel)); list.querySelector(".pal-item.on")?.scrollIntoView({ block: "nearest" }); } function runPalette(i) { const cmd = paletteItems[i == null ? paletteSel : i]; closePalette(); if (cmd) setTimeout(() => cmd.run(), 0); } function openPalette() { const el = $("#palette"); if (!el) return; el.hidden = false; paletteSel = 0; const input = $("#palette-input"); if (input) input.value = ""; renderPalette(""); input?.focus(); } function closePalette() { const el = $("#palette"); if (el) el.hidden = true; } function setupPalette() { const el = $("#palette"); if (!el) return; el.addEventListener("click", (e) => { if (e.target === el) closePalette(); }); const input = $("#palette-input"); const list = $("#palette-list"); input?.addEventListener("input", (e) => { paletteSel = 0; renderPalette(e.target.value); }); input?.addEventListener("keydown", (e) => { if (e.key === "ArrowDown") { e.preventDefault(); movePalette(1); } else if (e.key === "ArrowUp") { e.preventDefault(); movePalette(-1); } else if (e.key === "Enter") { e.preventDefault(); runPalette(); } else if (e.key === "Escape") { e.preventDefault(); closePalette(); } }); list?.addEventListener("click", (e) => { const item = e.target.closest(".pal-item"); if (item) runPalette(Number(item.dataset.i)); }); }