/* * sux sidebar — settings form, live validation, "Test connection", stats polling. * Settings persist through the background page (which owns storage and broadcasts * changes to the content scripts). */ /* global SuxShared, browser */ (function () { "use strict"; var SH = globalThis.SuxShared; function el(id) { return document.getElementById(id); } var fields = { enabled: el("enabled"), ruleAEnabled: el("ruleAEnabled"), word1: el("word1"), word2: el("word2"), ruleAHint: el("ruleAHint"), ruleBEnabled: el("ruleBEnabled"), ruleBDir: el("ruleBDir"), wordB: el("wordB"), ruleBHint: el("ruleBHint"), combine: el("combine"), apiKey: el("apiKey"), testKey: el("testKey"), testResult: el("testResult"), stJudged: el("stJudged"), stHidden: el("stHidden"), stWaiting: el("stWaiting"), diag: el("diag"), lastError: el("lastError"), permBanner: el("permBanner"), grantPerms: el("grantPerms"), permHint: el("permHint"), }; // Firefox treats MV3 host_permissions as opt-in: until they're granted the // content script never injects and the background can't reach the API. var REQUIRED_ORIGINS = ["https://api.typesafe.ai/*", "*://bsky.app/*"]; function checkPerms() { return browser.permissions.getAll().then(function (all) { var granted = REQUIRED_ORIGINS.every(function (o) { return (all.origins || []).indexOf(o) !== -1; }); fields.permBanner.hidden = granted; return granted; }).catch(function () { return true; }); } fields.grantPerms.addEventListener("click", function () { browser.permissions.request({ origins: REQUIRED_ORIGINS }).then(function (ok) { fields.permHint.textContent = ok ? "Granted. Reload your bsky.app tabs to start filtering." : "Permission declined — the filter will stay inactive."; fields.permHint.classList.toggle("ok", !!ok); checkPerms(); }).catch(function (err) { fields.permHint.textContent = "Request failed: " + String((err && err.message) || err); }); }); var saveTimer = null; function readForm() { return { enabled: fields.enabled.checked, apiKey: fields.apiKey.value.trim(), combine: fields.combine.value === "or" ? "or" : "and", ruleA: { enabled: fields.ruleAEnabled.checked, word1: fields.word1.value, word2: fields.word2.value, }, ruleB: { enabled: fields.ruleBEnabled.checked, word: fields.wordB.value, above: fields.ruleBDir.value !== "below", }, }; } function writeForm(s) { fields.enabled.checked = s.enabled; fields.ruleAEnabled.checked = s.ruleA.enabled; fields.word1.value = s.ruleA.word1; fields.word2.value = s.ruleA.word2; fields.ruleBEnabled.checked = s.ruleB.enabled; fields.ruleBDir.value = s.ruleB.above ? "above" : "below"; fields.wordB.value = s.ruleB.word; fields.combine.value = s.combine; fields.apiKey.value = s.apiKey; validate(); } function validate() { var st = SH.normalizeSettings(readForm()); var aOk = SH.ruleAValid(st); var bOk = SH.ruleBValid(st); fields.ruleAHint.textContent = st.ruleA.enabled ? (aOk ? "" : "needs two different words") : ""; fields.ruleBHint.textContent = st.ruleB.enabled ? (bOk ? "" : "needs a word") : ""; return st; } function saveSoon() { validate(); if (saveTimer) clearTimeout(saveTimer); saveTimer = setTimeout(function () { saveTimer = null; browser.runtime.sendMessage({ type: "saveSettings", settings: readForm() }).catch(function () { /* bg gone */ }); }, 250); } ["enabled", "ruleAEnabled", "ruleBEnabled", "ruleBDir", "combine"].forEach(function (id) { fields[id].addEventListener("change", saveSoon); }); ["word1", "word2", "wordB"].forEach(function (id) { fields[id].addEventListener("input", saveSoon); }); fields.apiKey.addEventListener("input", function () { fields.testResult.textContent = ""; fields.testResult.classList.remove("ok"); saveSoon(); }); fields.testKey.addEventListener("click", function () { var key = fields.apiKey.value.trim(); fields.testResult.classList.remove("ok"); if (!key) { fields.testResult.textContent = "Enter an API key first."; return; } fields.testKey.disabled = true; fields.testResult.textContent = "Testing…"; browser.runtime .sendMessage({ type: "testKey", apiKey: key }) .then(function (res) { if (res && res.ok) { fields.testResult.textContent = "Connected (" + (res.model || "jev") + ")."; fields.testResult.classList.add("ok"); } else { fields.testResult.textContent = (res && res.error) || "Test failed."; } }) .catch(function (err) { fields.testResult.textContent = "Test failed: " + String((err && err.message) || err); }) .then(function () { fields.testKey.disabled = false; }); }); setInterval(function () { checkPerms(); browser.runtime .sendMessage({ type: "getStats" }) .then(function (res) { if (!res) return; fields.stJudged.textContent = res.stats.judged; fields.stHidden.textContent = res.stats.hidden; fields.stWaiting.textContent = res.stats.waiting; fields.lastError.textContent = res.stats.lastError || ""; if (res.content) { var c = res.content; fields.diag.textContent = "feed: extracted " + ((c.domExtracted || 0) + (c.indexed || 0)) + " · items " + (c.items || 0) + " · hidden " + (c.hidden || 0) + " · unmatched " + (c.unmatchedAnchors || 0) + " · active " + (c.settingsActive ? "yes" : "no") + " · interceptor " + (c.interceptorInjected ? "up" : "down"); } }) .catch(function () { /* background restarting */ }); }, 1000); browser.runtime .sendMessage({ type: "getSettings" }) .then(function (res) { if (res && res.settings) writeForm(SH.normalizeSettings(res.settings)); }) .catch(function () { /* ignore */ }); checkPerms(); })();