// SPDX-License-Identifier: GPL-3.0-or-later /* === Overview sidebar: score breakdown bars + severity/category nav === */ function renderSidebar(data, counts) { const sidebar = $("#sidebar"); if (!sidebar) return; const findings = data.findings || []; sidebar.hidden = false; renderBreakdownBars(data); renderSeverityNav(counts); renderCategoryNav(findings); } /* "Why this score": top penalized findings as proportional bars. Mirrors the CLI's SCORE line (top-3 + "+N more") using the same server-computed scoreBreakdown — never re-derived here. */ function renderBreakdownBars(data) { const el = $("#sb-breakdown"); if (!el) return; const rows = (data.scoreBreakdown || []).filter((b) => b.penalty > 0) .sort((a, b) => b.penalty - a.penalty); if (!rows.length) { el.innerHTML = '
\u2713 No score penalties
'; return; } const TOP = 3; const max = rows[0].penalty; const shown = rows.slice(0, TOP); el.innerHTML = shown.map((b) => { const pct = Math.max(6, Math.round((b.penalty / max) * 100)); return '
' + '
' + esc(b.title || b.code || "") + '' + '" + "\u2212" + b.penalty + "
" + '
' + esc(b.code || "") + '
' + '
' + "
"; }).join("") + (rows.length > TOP ? '
+' + (rows.length - TOP) + " more
" : ""); } function renderSeverityNav(counts) { const el = $("#sb-sevnav"); if (!el) return; el.innerHTML = SEV_ORDER.map((sev) => { const n = counts[sev] || 0; if (!n) return ""; return '"; }).join("") || '
Nothing needs attention
'; } function renderCategoryNav(findings) { const el = $("#sb-catnav"); if (!el) return; const tally = new Map(); for (const f of findings) { const cat = categoryOf(f); tally.set(cat, (tally.get(cat) || 0) + 1); } el.innerHTML = CATEGORY_ORDER.map((cat) => { const n = tally.get(cat) || 0; if (!n) return ""; return '"; }).join(""); } /* Switch grouping mode and re-render the current report in place. The choice persists across sessions. */ function setGroupBy(mode) { if (mode !== "severity" && mode !== "category") return; try { localStorage.setItem("ld-groupby", mode); } catch {} if (groupBy === mode) return; groupBy = mode; document.querySelectorAll(".segbtn").forEach((b) => { const on = b.dataset.groupby === mode; b.classList.toggle("active", on); b.setAttribute("aria-pressed", String(on)); }); if (lastData) render(lastData); } function setupSidebar() { // Restore the grouping chosen in a previous session, before the first // render paints the wrong mode and has to flip. try { const saved = localStorage.getItem("ld-groupby"); if (saved === "severity" || saved === "category") { groupBy = saved; document.querySelectorAll(".segbtn").forEach((b) => b.classList.toggle("active", b.dataset.groupby === saved)); } } catch {} document.querySelectorAll(".segbtn").forEach((b) => b.setAttribute("aria-pressed", String(b.dataset.groupby === groupBy))); document.addEventListener("click", (e) => { // Segmented control const seg = e.target.closest(".segbtn"); if (seg) { setGroupBy(seg.dataset.groupby); return; } // Sidebar severity row → severity view + that filter const sevBtn = e.target.closest("[data-nav-sev]"); if (sevBtn) { setGroupBy("severity"); activeFilter = sevBtn.dataset.navSev; document.querySelectorAll(".fpill").forEach((b) => b.classList.toggle("active", b.dataset.sev === activeFilter)); syncGroupsOpen(); applyFilters(); scrollToGroup("sev", activeFilter); return; } // Sidebar category row → category view + open that group const catBtn = e.target.closest("[data-nav-cat]"); if (catBtn) { setGroupBy("category"); requestAnimationFrame(() => scrollToGroup("cat", catBtn.dataset.navCat)); } }); } function scrollToGroup(type, key) { const g = document.querySelector('#report .group[data-type="' + type + '"][data-key="' + key.replace(/"/g, '\\"') + '"]'); if (!g) return; g.setAttribute("open", ""); g.scrollIntoView({ behavior: scrollBehavior(), block: "start" }); }