// Preload shared by every add-on sidebar panel. Exposes a small, safe // surface to the add-on's HTML. Main-side handlers derive the add-on // identity from the sender's URL (the panel is always loaded from // somewhere inside /addons//), so a random page that // happens to see the API shape can't touch another add-on's storage. const { contextBridge, ipcRenderer } = require("electron"); contextBridge.exposeInMainWorld("silentmode", { storage: { get: (key, fallback = null) => ipcRenderer.invoke("addon-storage-get", key, fallback), set: (key, value) => ipcRenderer.invoke("addon-storage-set", key, value), all: () => ipcRenderer.invoke("addon-storage-all"), }, // Ask main which panel is currently visible — panels may want to // suspend expensive work when hidden. onVisibility: (cb) => ipcRenderer.on("sidebar-visibility", (_e, visible) => cb(!!visible)), // Call into the add-on's activate() context: resolves with whatever the // matching api.onMessage handler returned (or rejects with its error). invoke: (msg, payload) => ipcRenderer.invoke("addon-msg", String(msg), payload), // Events the add-on pushes via api.emit while this panel is open. on: (msg, cb) => ipcRenderer.on("addon-event", (_e, name, payload) => { if (name === msg) cb(payload); }), }); // -- Panel picker strip ----------------------------------------------------- // Injected at the top of every panel document so the user can switch between // registered extension panels without a settings round-trip. Only appears // when 2+ panels exist. Main pushes the panel list here via IPC on the // initial sidebar-state event. async function installPickerStrip() { let state; try { state = await ipcRenderer.invoke("sidebar-state"); } catch { return; } const panels = (state && state.panels) || []; if (panels.length < 2) return; const strip = document.createElement("div"); strip.setAttribute("aria-label", "Extension panels"); strip.style.cssText = [ "position:fixed", "top:0", "left:5px", "right:0", "height:32px", "display:flex", "align-items:stretch", "gap:1px", "background:rgba(11,14,20,.85)", "backdrop-filter:blur(8px)", "border-bottom:1px solid rgba(255,255,255,.10)", "z-index:2147483646", "user-select:none", "font:12px/1 system-ui,-apple-system,Segoe UI,Roboto,sans-serif", ].join(";"); const active = state.active; for (const p of panels) { const tab = document.createElement("button"); tab.type = "button"; const isActive = p.panelId === active; tab.style.cssText = [ "flex:1", "min-width:0", "padding:0 10px", "border:0", "background:" + (isActive ? "rgba(214,255,61,.10)" : "transparent"), "color:" + (isActive ? "#d6ff3d" : "#8b98a9"), "cursor:pointer", "display:flex", "align-items:center", "gap:6px", "justify-content:center", "overflow:hidden", "text-overflow:ellipsis", "white-space:nowrap", "font:inherit", ].join(";"); tab.innerHTML = '' + (p.icon || "🧩") + '' + p.title.replace(/'; tab.addEventListener("click", () => ipcRenderer.invoke("sidebar-open", p.panelId)); strip.appendChild(tab); } document.body.appendChild(strip); // Push the panel body down so the 32-px strip doesn't overlap the content. // box-sizing:border-box keeps 100%-tall panels from overflowing when we // add the padding. const style = document.createElement("style"); style.textContent = "html,body{box-sizing:border-box}body{padding-top:33px!important}"; document.head.appendChild(style); } // Sidebar resize grip. Injected into every panel automatically so panel // authors don't have to reinvent it. A thin strip along the LEFT edge // (the boundary between the tab area and the sidebar) accepts mousedown // and streams drag deltas to main until mouseup. Main clamps the width // to [200, 800] and persists it in settings.sidebarWidth. window.addEventListener("DOMContentLoaded", () => { installPickerStrip(); const grip = document.createElement("div"); grip.setAttribute("aria-label", "Resize sidebar"); grip.style.cssText = [ "position:fixed", "left:0", "top:0", "bottom:0", "width:5px", "cursor:col-resize", "z-index:2147483647", "background:transparent", ].join(";"); // A faint highlight on hover so the affordance is visible. grip.addEventListener("mouseenter", () => { grip.style.background = "rgba(214,255,61,.20)"; }); grip.addEventListener("mouseleave", () => { if (!dragging) grip.style.background = "transparent"; }); document.body.appendChild(grip); let dragging = false; grip.addEventListener("mousedown", (e) => { if (e.button !== 0) return; e.preventDefault(); dragging = true; document.body.style.userSelect = "none"; grip.style.background = "rgba(214,255,61,.35)"; }); window.addEventListener("mousemove", (e) => { if (!dragging) return; // Moving cursor LEFT = grow sidebar width. movementX is negative left. if (e.movementX !== 0) ipcRenderer.invoke("sidebar-drag", -e.movementX); }); const stop = () => { if (!dragging) return; dragging = false; document.body.style.userSelect = ""; grip.style.background = "transparent"; }; window.addEventListener("mouseup", stop); window.addEventListener("mouseleave", stop); });