Sites you installed as apps. Each opens in its own window with its own taskbar button and shortcuts, and runs on the same Theseus underneath: same names, same protections, same sign-ins.
@@ -1148,7 +1170,8 @@
})();
const TOGGLES = ["restoreSession", "backgroundThrottle", "freezeBackgroundTabs", "extensionsOnDemand", "walletAtLaunch", "preloadMenus", "blockCamera", "blockMicrophone", "hideMediaDevices", "gpc",
"clearCookiesOnQuit", "clearCacheOnQuit", "clearStorageOnQuit", "clearHistoryOnQuit",
- "quickLinksShow", "translateAutoOffer"];
+ "quickLinksShow", "translateAutoOffer",
+ "sidebarHideOnNewTab", "sidebarHideOnRightPanel", "sidebarHideOnPageClick"];
// Text inputs that round-trip through settings-set on change. Trimmed; a
// cleared field writes an empty string, which main re-defaults from
// SETTINGS_DEFAULTS on next launch.
@@ -1174,7 +1197,12 @@
const addBtn = document.getElementById("qlAdd");
if (!list || !addBtn) return;
const esc = (v) => String(v || "").replace(/[&<>"']/g, (c) => ({ "&":"&","<":"<",">":">","\"":""","'":"'" })[c]);
+ // A row's pin and width share their key with the strip: "q:".
+ const pinBtnHtml = (on, key) => ``;
+ const widthBtnHtml = (w, key) => w ? `` : "";
+ let cur = s;
function render(links) {
+ const pinned = cur.leftPanelPinned || {}, widths = cur.leftPanelWidths || {};
list.innerHTML = "";
if (!links.length) {
const empty = document.createElement("div");
@@ -1182,19 +1210,59 @@
list.appendChild(empty);
return;
}
- for (const L of links) {
+ links.forEach((L, i) => {
+ const key = "q:" + L.id;
const row = document.createElement("div");
- row.style.cssText = "display:flex;align-items:center;gap:8px;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:8px 12px";
- row.innerHTML = `${esc((L.title || "?").slice(0,1).toUpperCase())}
- ${esc(L.title || L.url)}
${esc(L.url)}
+ // Same drag-to-reorder rows as Search engines (.eng styles).
+ row.className = "eng";
+ 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 = `⠿${esc((L.title || "?").slice(0,1).toUpperCase())}
+ ${esc(L.title || L.url)}
${esc(L.url)}${widths[key] ? " · " + Number(widths[key]) + " px" : ""}
+ ${pinBtnHtml(!!pinned[key], key)}${widthBtnHtml(widths[key], key)}
`;
list.appendChild(row);
- }
+ });
list.querySelectorAll("[data-del]").forEach((b) => b.onclick = async () => {
- const cur = (await C.get()).quickLinks || [];
- C.set("quickLinks", cur.filter((x) => x.id !== b.dataset.del));
+ const now = (await C.get()).quickLinks || [];
+ C.set("quickLinks", now.filter((x) => x.id !== b.dataset.del));
+ });
+ // Drag to reorder; the strip follows through settings-set.
+ let dragId = null;
+ list.querySelectorAll(".eng").forEach((row) => {
+ row.addEventListener("dragstart", (e) => { dragId = row.dataset.id; e.dataTransfer.effectAllowed = "move"; row.classList.add("dragging"); });
+ row.addEventListener("dragend", () => { row.classList.remove("dragging"); list.querySelectorAll(".eng").forEach((r) => r.classList.remove("over")); });
+ row.addEventListener("dragover", (e) => { e.preventDefault(); e.dataTransfer.dropEffect = "move"; if (row.dataset.id !== dragId) row.classList.add("over"); });
+ row.addEventListener("dragleave", () => row.classList.remove("over"));
+ row.addEventListener("drop", async (e) => {
+ e.preventDefault(); row.classList.remove("over");
+ if (!dragId || dragId === row.dataset.id) return;
+ const now = ((await C.get()).quickLinks || []).slice();
+ const from = now.findIndex((x) => x.id === dragId), to = now.findIndex((x) => x.id === row.dataset.id);
+ if (from < 0 || to < 0) return;
+ const [moved] = now.splice(from, 1);
+ now.splice(to, 0, moved);
+ C.set("quickLinks", now);
+ });
+ });
+ wirePinWidth(list);
+ }
+ // Shared with the extension-panel list below.
+ window.__sbWirePinWidth = wirePinWidth;
+ function wirePinWidth(root) {
+ root.querySelectorAll("[data-pin]").forEach((b) => b.onclick = async () => {
+ const p = { ...((await C.get()).leftPanelPinned || {}) };
+ if (p[b.dataset.pin]) delete p[b.dataset.pin]; else p[b.dataset.pin] = true;
+ C.set("leftPanelPinned", p);
+ });
+ root.querySelectorAll("[data-width]").forEach((b) => b.onclick = async () => {
+ const w = { ...((await C.get()).leftPanelWidths || {}) };
+ delete w[b.dataset.width];
+ C.set("leftPanelWidths", w);
});
}
+ window.__sbButtons = { pinBtnHtml, widthBtnHtml };
render(s.quickLinks || []);
addBtn.onclick = async () => {
const t = String(titleIn.value || "").trim();
@@ -1209,7 +1277,37 @@
titleIn.value = ""; urlIn.value = "";
};
// Live-sync from main (edits from other surfaces, or the strip's own add).
- if (C.onSettingsUpdate) C.onSettingsUpdate((next) => render(next.quickLinks || []));
+ if (C.onSettingsUpdate) C.onSettingsUpdate((next) => { cur = next; render(next.quickLinks || []); });
+ })();
+ // ---- Extension panels on the strip (Sidebar) ---------------------------
+ (function () {
+ const box = document.getElementById("sbPanels");
+ if (!box || !C.sidebarLeftPanels) return;
+ const esc = (v) => String(v || "").replace(/[&<>"']/g, (c) => ({ "&":"&","<":"<",">":">","\"":""","'":"'" })[c]);
+ let cur = s;
+ async function render() {
+ let panels = []; try { panels = (await C.sidebarLeftPanels()) || []; } catch {}
+ const pinned = cur.leftPanelPinned || {}, widths = cur.leftPanelWidths || {};
+ const B = window.__sbButtons;
+ if (!panels.length) { box.innerHTML = `No extension opens from the strip.
`; return; }
+ box.innerHTML = panels.map((p) => {
+ 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" : ""}
+ ${B ? B.pinBtnHtml(!!pinned[p.id], p.id) + B.widthBtnHtml(widths[p.id], 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 (window.__sbWirePinWidth) window.__sbWirePinWidth(box);
+ }
+ render();
+ document.addEventListener("section", (e) => { if (String(e.detail) === "sidebar") render(); });
+ if (C.onSettingsUpdate) C.onSettingsUpdate((next) => { cur = next; render(); });
})();
// ---- Translation-peer list editor (General) ---------------------------
// Same shape as the Quick-links editor above: add a row, trash a row,
@@ -2530,6 +2628,7 @@
(a.icon ? `
` : `🧩`) +
`${escA(a.name)}${escA(a.host)}${when ? " · installed " + escA(when) : ""}${a.open ? " · open now" : ""}
` +
`` +
+ `` +
`