diff --git a/main.js b/main.js index c5fd2b9a..3e6d7666 100644 --- a/main.js +++ b/main.js @@ -9172,6 +9172,7 @@ ipcMain.handle("settings-set", (_e, key, val) => { activeQuickLinkId = null; } if (leftPanelId && leftPanelHidden(leftPanelId)) closeLeftPanel(); + if (activeQuickLinkId && quickLinkHidden(activeQuickLinkId)) closeQuickPanel(); if (key === "quickLinksShow" && !settings.quickLinksShow) closeLeftPanel(); layout(); emitQuickLinksState(); @@ -9191,7 +9192,7 @@ ipcMain.handle("settings-set", (_e, key, val) => { // The strip, top to bottom. A saved order keeps what the user dragged; an // item it does not know yet (a new web app or extension) goes at the end. function stripOrder() { - const links = (settings.quickLinks || []).map((L) => "q:" + L.id); + const links = visibleQuickLinks().map((L) => "q:" + L.id); const panels = leftPanels().filter((p) => !leftPanelHidden(p.panelId)).map((p) => "p:" + p.panelId); const all = new Set([...links, ...panels]); const saved = Array.isArray(settings.stripOrder) ? settings.stripOrder.filter((k) => all.has(k)) : []; @@ -9219,7 +9220,7 @@ ipcMain.handle("quicklinks-reorder", (e, order) => { function quickLinksPayload() { return { order: stripOrder(), - links: settings.quickLinks || [], + links: visibleQuickLinks(), openId: leftPanelId ? "p:" + leftPanelId : activeQuickLinkId, panels: leftPanels().filter((p) => !leftPanelHidden(p.panelId)).map((p) => ({ id: "p:" + p.panelId, title: p.addonName || p.title, icon: p.icon || "" })), }; @@ -9382,7 +9383,16 @@ function quickLinkFor(url) { if (!settings.quickLinksShow) return null; let host; try { host = new URL(url).host; } catch { return null; } - return (settings.quickLinks || []).find((L) => { try { return new URL(L.url).host === host; } catch { return false; } }) || null; + return visibleQuickLinks().find((L) => { try { return new URL(L.url).host === host; } catch { return false; } }) || null; +} +// A web app switched off in Settings › Sidebar stays in the list (with its +// sign-in and width) but is off the strip. Same store as extension panels: +// leftPanelsHidden, keyed "q:" / "p:". +function quickLinkHidden(id) { + return Array.isArray(settings.leftPanelsHidden) && settings.leftPanelsHidden.includes("q:" + id); +} +function visibleQuickLinks() { + return (settings.quickLinks || []).filter((L) => L && !quickLinkHidden(L.id)); } // at: an address on the app's site to show (a share screen, a chat), even diff --git a/settings.html b/settings.html index db60e6b3..af302b6b 100644 --- a/settings.html +++ b/settings.html @@ -955,7 +955,7 @@

Web apps

-

Sites that open in the panel. A site you installed as an app can be added from Apps.

+

Sites that open in the panel. Switch one off to take it off the strip; it stays here, still signed in, for switching back on. A site you installed as an app can be added from Apps.

@@ -965,7 +965,7 @@

Extension panels

-

Extensions that open from the strip. Hide one to take it off the strip; the extension keeps working.

+

Extensions that open from the strip. Switch one off to take it off the strip; the extension keeps working.

@@ -1304,6 +1304,16 @@ const esc = (v) => String(v || "").replace(/[&<>"']/g, (c) => ({ "&":"&","<":"<",">":">","\"":""","'":"'" })[c]); // A row's width shares its key with the strip: "q:". const widthBtnHtml = (w, key) => w ? `` : ""; + // On/off: whether the app or extension panel is on the strip. Off keeps + // it here, with its sign-in and width, for switching back on. + const onStripHtml = (on, key) => ``; + function wireOnStrip(root) { + root.querySelectorAll("[data-onstrip]").forEach((i) => i.onchange = async () => { + const h = new Set((await C.get()).leftPanelsHidden || []); + if (i.checked) h.delete(i.dataset.onstrip); else h.add(i.dataset.onstrip); + C.set("leftPanelsHidden", [...h]); + }); + } let cur = s; function render(links) { const widths = cur.leftPanelWidths || {}; @@ -1322,10 +1332,12 @@ row.draggable = true; row.dataset.id = L.id; row.style.cssText = "display:flex;align-items:center;gap:8px;flex-wrap:wrap;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:8px 12px"; - row.innerHTML = `⠿${iconBox(L)} -
${esc(L.title || L.url)}
${esc(L.url)}${widths[key] ? " · " + Number(widths[key]) + " px" : ""}
+ const on = !(cur.leftPanelsHidden || []).includes(key); + row.innerHTML = `⠿${iconBox(L)} +
${esc(L.title || L.url)}
${on ? "" : "Off the strip · "}${esc(L.url)}${widths[key] ? " · " + Number(widths[key]) + " px" : ""}
${widthBtnHtml(widths[key], key)} - `; + + ${onStripHtml(on, key)}`; list.appendChild(row); }); list.querySelectorAll("[data-del]").forEach((b) => b.onclick = async () => { @@ -1351,6 +1363,7 @@ }); }); wireWidth(list); + wireOnStrip(list); // A site without a bundled icon whose favicon does not load: its letter. list.querySelectorAll("img[data-fav]").forEach((img) => { img.onerror = () => { const d = img.parentElement; img.remove(); d.textContent = d.dataset.letter || "?"; }; }); } @@ -1373,7 +1386,7 @@ C.set("leftPanelWidths", w); }); } - window.__sbButtons = { widthBtnHtml }; + window.__sbButtons = { widthBtnHtml, onStripHtml, wireOnStrip }; render(s.quickLinks || []); // Typing a well-known app's name shows the rest of it in grey, and its // address in the address field; → at the end of a field takes the grey @@ -1463,15 +1476,11 @@ const icon = /^data:image\//.test(p.icon) ? `` : esc(p.icon || (p.title || "?").slice(0, 1)); return `
${icon}
-
${esc(p.title)}
${p.hidden ? "Not on the strip" : "On the strip"}${widths[p.id] ? " · " + Number(widths[p.id]) + " px" : ""}
+
${esc(p.title)}
${p.hidden ? "Off the strip" : "On the strip"}${widths[p.id] ? " · " + Number(widths[p.id]) + " px" : ""}
${B ? B.widthBtnHtml(widths[p.id], p.id) : ""} -
`; + ${B ? B.onStripHtml(!p.hidden, p.id) : ""}`; }).join(""); - box.querySelectorAll("[data-show]").forEach((b) => b.onclick = async () => { - const h = new Set((await C.get()).leftPanelsHidden || []); - if (h.has(b.dataset.show)) h.delete(b.dataset.show); else h.add(b.dataset.show); - C.set("leftPanelsHidden", [...h]); - }); + if (B) B.wireOnStrip(box); if (window.__sbWireWidth) window.__sbWireWidth(box); } render();