Spaces:
Sleeping
Sleeping
File size: 5,985 Bytes
9548e93 dd28267 9548e93 bdba541 dd28267 bdba541 dd28267 bdba541 9548e93 bdba541 9548e93 bdba541 9548e93 dd28267 9548e93 dd28267 bdba541 9548e93 bdba541 9548e93 bdba541 9548e93 bdba541 9548e93 bdba541 9548e93 bdba541 dd28267 bdba541 9548e93 dd28267 bdba541 9548e93 bdba541 9548e93 bdba541 9548e93 bdba541 9548e93 bdba541 9548e93 bdba541 9548e93 bdba541 9548e93 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | /**
* ClauseGuard — Side Panel v4.3
*
* FIXED v4.3: Added CRITICAL severity support (filter, cards, icons, descriptions).
* FIXED v4.3: Severity ordering now uses numeric mapping consistently.
*/
const DESCS = {
"Limitation of liability": "Company limits or excludes liability for losses or damages.",
"Unilateral termination": "They can close your account without reason.",
"Unilateral change": "Terms can change without your consent.",
"Content removal": "Your content can be deleted without notice.",
"Contract by using": "You agree just by visiting or using the site.",
"Choice of law": "Foreign law applies instead of your local protections.",
"Jurisdiction": "Disputes handled in their preferred court.",
"Arbitration": "You waive your right to sue in court.",
};
// Severity numeric ordering (higher = more severe)
const SEV_ORDER = { CRITICAL: 4, HIGH: 3, MEDIUM: 2, LOW: 1 };
// SVG icons for severity
const SEV_ICONS = {
CRITICAL: '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>',
HIGH: '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>',
MEDIUM: '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg>',
LOW: '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>',
};
let allClauses = [];
let currentFilter = "all";
async function loadResults() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) return;
const results = await chrome.runtime.sendMessage({ type: "GET_RESULTS", tabId: tab.id });
if (!results || !results.results) {
document.getElementById("empty-state").style.display = "block";
return;
}
document.getElementById("empty-state").style.display = "none";
document.getElementById("score-bar").style.display = "flex";
document.getElementById("filters").style.display = "flex";
// Meta
document.getElementById("meta").textContent = `${results.total_clauses} clauses · ${results.flagged_count} flagged`;
// Grade
const gb = document.getElementById("grade-box");
gb.textContent = results.grade;
gb.className = `grade-box grade-${results.grade.toLowerCase()}`;
// Score
document.getElementById("score-num").textContent = `${results.risk_score} / 100`;
const pf = document.getElementById("progress-fill");
pf.style.width = `${results.risk_score}%`;
pf.style.background = results.risk_score >= 60 ? "#ef4444" : results.risk_score >= 30 ? "#f59e0b" : "#22c55e";
// FIX v4.3: Count CRITICAL severity too
const counts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
const flagged = results.results.filter(r => r.categories?.length > 0);
flagged.forEach(r => r.categories.forEach(c => {
if (counts[c.severity] !== undefined) counts[c.severity]++;
else counts.MEDIUM++; // Default unknown to MEDIUM
}));
// Show CRITICAL count in the filter if any exist
const fcCrit = document.getElementById("fc-crit");
const critFilter = document.getElementById("filter-critical");
if (fcCrit) fcCrit.textContent = counts.CRITICAL;
if (critFilter) critFilter.style.display = counts.CRITICAL > 0 ? "flex" : "none";
document.getElementById("fc-high").textContent = counts.HIGH;
document.getElementById("fc-med").textContent = counts.MEDIUM;
document.getElementById("fc-low").textContent = counts.LOW;
allClauses = flagged;
renderClauses();
}
function renderClauses() {
const list = document.getElementById("clause-list");
const filtered = currentFilter === "all" ? allClauses : allClauses.filter(c => c.categories.some(cat => cat.severity === currentFilter));
if (filtered.length === 0) {
list.innerHTML = '<div style="text-align:center;padding:24px;color:#a1a1aa;font-size:12px;">No clauses match this filter.</div>';
return;
}
list.innerHTML = filtered.map((clause, i) => {
const maxSev = clause.categories.reduce((m, c) => {
return (SEV_ORDER[c.severity] || 0) > (SEV_ORDER[m] || 0) ? c.severity : m;
}, "LOW");
const tagMap = { CRITICAL: "tag-critical", HIGH: "tag-high", MEDIUM: "tag-medium", LOW: "tag-low" };
const tags = clause.categories.map(c =>
`<span class="tag ${tagMap[c.severity] || "tag-medium"}">${SEV_ICONS[c.severity] || ""} ${c.name}</span>`
).join("");
const descs = clause.categories.map(c =>
`<div class="clause-desc">${DESCS[c.name] || c.name}</div>`
).join("");
const text = clause.text.length > 200 ? clause.text.slice(0, 200) + "…" : clause.text;
return `
<div class="clause-card sev-${maxSev.toLowerCase()}">
<div class="clause-tags">${tags}</div>
<div class="clause-text">${text}</div>
${descs}
</div>
`;
}).join("");
}
// Filters
document.getElementById("filters").addEventListener("click", (e) => {
const btn = e.target.closest(".filter-btn");
if (!btn) return;
document.querySelectorAll(".filter-btn").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
currentFilter = btn.dataset.filter;
renderClauses();
});
// Listen for updates
chrome.storage.onChanged.addListener((changes) => {
for (const key of Object.keys(changes)) {
if (key.startsWith("results_")) { loadResults(); break; }
}
});
loadResults();
|