diff --git a/bundled-addons/blocker/index.js b/bundled-addons/blocker/index.js index 0f1bd868..78219e50 100644 --- a/bundled-addons/blocker/index.js +++ b/bundled-addons/blocker/index.js @@ -193,7 +193,8 @@ async function activate(a) { persistAllow(); pushState(); return snapshot(); }); api.onMessage("refresh-lists", () => refreshLists("manual")); - api.onMessage("open-settings", () => { api.openSettings("performance"); return true; }); + api.onMessage("open-settings", () => { api.openSettings("privacy"); return true; }); + api.onMessage("list-exceptions", () => [...allow].sort()); // Filter first, engine second: requests made while the engine builds pass // through (a few hundred milliseconds on first run), which beats holding diff --git a/bundled-addons/consent/index.js b/bundled-addons/consent/index.js index 579ee015..ad6eb509 100644 --- a/bundled-addons/consent/index.js +++ b/bundled-addons/consent/index.js @@ -88,7 +88,8 @@ async function activate(a) { }); // From the panel. api.onMessage("state", () => snapshot()); - api.onMessage("open-settings", () => { api.openSettings("performance"); return true; }); + api.onMessage("open-settings", () => { api.openSettings("privacy"); return true; }); + api.onMessage("list-exceptions", () => [...skip].sort()); api.onMessage("set-enabled", (p) => { enabled = !!(p && p.enabled); api.storage.set("enabled", enabled); pushState(); return snapshot(); }); api.onMessage("set-mode", (p) => { mode = p && p.mode === "optIn" ? "optIn" : "optOut"; api.storage.set("mode", mode); pushState(); return snapshot(); }); api.onMessage("skip-site", (p) => { diff --git a/main.js b/main.js index ef55147b..21649456 100644 --- a/main.js +++ b/main.js @@ -2134,7 +2134,7 @@ function initAddons() { // so the same section-hint validation applies. Add-ons hit this when they // want to point users at Passwords, Extensions, etc. openSettings: (section) => { - const slug = typeof section === "string" && /^[a-z0-9-]{1,32}$/i.test(section) ? section.toLowerCase() : ""; + const slug = typeof section === "string" && /^[a-z0-9-]{1,32}(?:\/[a-z0-9-]{1,32})?$/i.test(section) ? section.toLowerCase() : ""; const ex = tabs.find((t) => t.settings); if (ex) { setActive(ex.id); @@ -3570,7 +3570,7 @@ async function navigateTab(id, input) { if (installId) { installExtensionWithConsent(installId, null); return; } // theseus://settings, theseus://settings/
: a linkable address for // every Settings page. - const settingsLink = /^theseus:\/\/settings(?:\/([a-z0-9-]{1,32}))?\/?$/i.exec(q); + const settingsLink = /^theseus:\/\/settings(?:\/([a-z0-9-]{1,32}(?:\/[a-z0-9-]{1,32})?))?\/?$/i.exec(q); if (settingsLink) { openSettingsTab(settingsLink[1] || ""); return; } // Local paths open as files — never BCNR, never a search. const fileUrl = localFileUrl(q); @@ -4912,7 +4912,7 @@ ipcMain.handle("toggle-tor", () => { torState === "off" ? startTor() : stopTor() // settings.html watches for a fragment on load and an IPC message when // already loaded. function openSettingsTab(section) { - const slug = typeof section === "string" && /^[a-z0-9-]{1,32}$/i.test(section) ? section.toLowerCase() : ""; + const slug = typeof section === "string" && /^[a-z0-9-]{1,32}(?:\/[a-z0-9-]{1,32})?$/i.test(section) ? section.toLowerCase() : ""; const ex = tabs.find((t) => t.settings); if (ex) { setActive(ex.id); @@ -4930,6 +4930,17 @@ ipcMain.handle("addon-invoke", (e, id, msg, payload) => { if (!addonHost) throw new Error("no add-on host"); return addonHost.dispatch(String(id || ""), String(msg || ""), payload, { from: "settings" }); }); +// The Settings page reports which page it shows; the address bar follows +// (theseus://settings/), so every Settings page has a link. +ipcMain.handle("settings-section", (e, slug) => { + const t = tabs.find((x) => x.settings && x.view?.webContents === e.sender); + if (!t) return false; + const s = typeof slug === "string" && /^[a-z0-9-]{1,32}(?:\/[a-z0-9-]{1,32})?$/i.test(slug) ? slug.toLowerCase() : ""; + t.url = s ? `theseus://settings/${s}` : "theseus://settings"; + emitTabs(); + return true; +}); +ipcMain.handle("tor-state", (e) => { if (!isSettingsSender(e)) throw new Error("tor-state: settings only"); return torState; }); ipcMain.handle("settings-open-panel", (e, panelId) => { if (!isSettingsSender(e)) throw new Error("settings-open-panel: settings only"); setSidebar(true, String(panelId || "")); diff --git a/settings-preload.js b/settings-preload.js index 5de2421d..d5ebc34d 100644 --- a/settings-preload.js +++ b/settings-preload.js @@ -53,6 +53,10 @@ contextBridge.exposeInMainWorld("cfg", { // handlers (Shield, Cookie Pop-ups) and open its panel for the details. addonInvoke: (id, msg, payload) => ipcRenderer.invoke("addon-invoke", String(id || ""), String(msg || ""), payload), openPanel: (panelId) => ipcRenderer.invoke("settings-open-panel", String(panelId || "")), + // Which page is showing (the address bar follows), Tor state + toggle for Privacy › Network. + reportSection: (slug) => ipcRenderer.invoke("settings-section", String(slug || "")), + torState: () => ipcRenderer.invoke("tor-state"), + toggleTor: () => ipcRenderer.invoke("toggle-tor"), openAddonsDir: () => ipcRenderer.invoke("addons-open-dir"), reloadAddons: () => ipcRenderer.invoke("addons-reload"), // Community extensions from theseus.x/extensions: the catalog (with what diff --git a/settings.html b/settings.html index f635eede..b52930ac 100644 --- a/settings.html +++ b/settings.html @@ -34,7 +34,17 @@ .row .acts{display:flex;gap:8px;align-items:center;margin-top:10px;flex-wrap:wrap} .btn.small{padding:5px 10px;font-size:12px} .btn.ghost{background:transparent;border-color:var(--line)} - h2.sub{font-size:12px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--mut);margin:22px 0 8px} + .crumb{font-size:13px;color:var(--mut);margin:0 0 .6rem}.crumb a{color:var(--acid-text);cursor:pointer;text-decoration:none}.crumb a:hover{text-decoration:underline} + h1 .back{border:none;background:transparent;color:var(--mut);font:inherit;font-size:1.3rem;line-height:1;cursor:pointer;padding:0 8px 0 0;vertical-align:-1px}h1 .back:hover{color:var(--ink)} + .status{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px 18px;margin-bottom:10px} + .status .h{font-weight:600;margin-bottom:8px} + .srow{display:flex;gap:10px;align-items:center;font-size:13.5px;color:var(--ink);padding:3px 0} + .srow::before{content:"";width:9px;height:9px;border-radius:50%;background:var(--dim);flex:none} + .srow.ok::before{background:#3ddc84}.srow.warn::before{background:#f5a524} + .row.link{cursor:pointer}.row.link:hover{border-color:rgb(from var(--acid) r g b / .4)}.row .chev{color:var(--dim);font-size:20px;line-height:1} + .exlist{display:flex;flex-direction:column;gap:6px;margin-bottom:14px} + .exrow{display:flex;align-items:center;justify-content:space-between;gap:12px;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:9px 14px;font:13px ui-monospace,"Cascadia Code",Consolas,monospace} + .exempty{color:var(--dim);font-size:13px;margin:0 0 14px} /* control column — mode select + optional value field on the same row so the dropdown menus don't get cut off underneath, wraps only when narrow */ .ctl{display:flex;flex-direction:row;flex-wrap:wrap;gap:6px;align-items:center;justify-content:flex-end;flex:none;max-width:60%} @@ -425,12 +435,24 @@ + + - - + +