diff --git a/home-preload.js b/home-preload.js index aa3b2c9b..10d76a37 100644 --- a/home-preload.js +++ b/home-preload.js @@ -5,6 +5,9 @@ // origin-mismatched call, so a third-party page can inspect the API's // SHAPE but can't invoke it against local user data. const { contextBridge, ipcRenderer } = require("electron"); +// A click in the page closes the left panel (web app / add-on) unless it is +// pinned. Main ignores this unless a panel is open and the tab is active. +window.addEventListener("pointerdown", () => { ipcRenderer.send("tab-pointerdown"); }, true); contextBridge.exposeInMainWorld("home", { getCards: () => ipcRenderer.invoke("home-cards-get"), setCards: (cards) => ipcRenderer.invoke("home-cards-set", cards), diff --git a/main.js b/main.js index 8e702709..c7544d6d 100644 --- a/main.js +++ b/main.js @@ -7951,6 +7951,7 @@ function quickLinksPayload() { return { links: settings.quickLinks || [], openId: leftPanelId ? "p:" + leftPanelId : activeQuickLinkId, + pinned: leftPanelPinned(), panels: leftPanels().map((p) => ({ id: "p:" + p.panelId, title: p.addonName || p.title, icon: p.icon || "" })), }; } @@ -7977,6 +7978,30 @@ function raisePanelGrip() { // Opera-style: the left panel steps aside when the user opens something else // (a new tab, the right sidebar). It is only hidden — the app keeps its // state, so reopening it is instant and lands where the user left off. +// Opera's other rule: a click in the page closes the panel, unless the user +// pinned that app (settings.leftPanelPinned, keyed like leftPanelWidths). +function leftPanelPinned() { + const k = leftPanelKey(); + return !!(k && settings.leftPanelPinned && settings.leftPanelPinned[k]); +} +ipcMain.handle("quicklinks-pin", (e, on) => { + if (!quicklinks || e.sender !== quicklinks.webContents) return false; + const k = leftPanelKey(); + if (!k) return false; + const next = { ...(settings.leftPanelPinned || {}) }; + if (on) next[k] = true; else delete next[k]; + settings.leftPanelPinned = next; + saveSettings(); + emitQuickLinksState(); + return !!on; +}); +// home-preload reports a pointerdown in a web tab's page. +ipcMain.on("tab-pointerdown", (e) => { + if (!leftPanelKey() || leftPanelPinned()) return; + const t = tabs.find((x) => x.view?.webContents === e.sender); + if (!t || t.id !== activeId) return; + hideLeftPanels(); +}); function hideLeftPanels() { if (activeQuickLinkId) closeQuickPanel(); if (leftPanelId) closeLeftPanel(); diff --git a/quicklinks-preload.js b/quicklinks-preload.js index 0033f6cc..a39811ed 100644 --- a/quicklinks-preload.js +++ b/quicklinks-preload.js @@ -3,6 +3,7 @@ contextBridge.exposeInMainWorld("quicklinks", { getState: () => ipcRenderer.invoke("quicklinks-get-state"), open: (id) => ipcRenderer.invoke("quicklinks-open", String(id || "")), close: () => ipcRenderer.invoke("quicklinks-close"), + pin: (on) => ipcRenderer.invoke("quicklinks-pin", !!on), add: () => ipcRenderer.invoke("quicklinks-add"), onState: (cb) => ipcRenderer.on("quicklinks-state", (_e, d) => cb(d)), }); diff --git a/quicklinks.html b/quicklinks.html index 6fdfff93..6b9ee357 100644 --- a/quicklinks.html +++ b/quicklinks.html @@ -39,12 +39,19 @@ .sep { width: 22px; height: 1px; background: var(--line2); margin: 4px 0; } .icon.emoji { font-size: 18px; } .add:hover { color: var(--acid); } + /* Pin: keeps the open panel when the page is clicked. Shown only while a + panel is open; lit when that app is pinned. */ + .pin { color: var(--mut); margin-bottom: 4px; } + .pin[hidden] { display: none; } + .pin.on { color: var(--acid); background: var(--acid-tint); } + .pin svg { width: 16px; height: 16px; }