theseus/preload.js
Local Dev 61ebe44dac fix(theseus/bookmarks): favicon + working edit-title (window.prompt is blocked)
Two bugs the user hit:

1) Edit title silently did nothing. window.prompt is disabled in
   Electron BrowserView contexts, so prompt() returned null and the
   round-trip remove-then-re-add never fired. Built an in-chrome
   modal (.promptmask/.promptbox) that resolves to the trimmed
   string on OK/Enter or null on Cancel/Escape/mask-click, and
   swapped the ctx-menu Edit action to use it. Added a real
   bookmark-update IPC (title + optional favicon merge) so the edit
   no longer round-trips through remove/add — same URL, just the
   title changes.

2) Bookmarks had no favicon. The bookmark data model was {title,url}
   only, and the row template had no icon slot. Extended:
     - Renderer tracks current.favicon from onTabs's active tab.
     - Star click and ctx "Add current page" pass favicon along.
     - main persists a favicon field (capped 2KB) and emits it.
     - Row template shows the favicon (14×14) with an onerror
       fallback so a broken data:/URL doesn't leave a broken glyph.
     - Backfill: when a currently-open URL matches a stale bookmark
       without favicon, the render pass patches it once via
       updateBookmark. Stops itself the next render.

Older bookmarks.json files stay valid — no schema migration needed;
missing favicon just renders the empty placeholder slot.
2026-09-04 22:12:03 +02:00

65 lines
4.1 KiB
JavaScript

const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("theseus", {
navigate: (input) => ipcRenderer.invoke("navigate", input),
search: (q) => ipcRenderer.invoke("search", q),
newTab: () => ipcRenderer.invoke("new-tab"),
closeTab: (id) => ipcRenderer.invoke("close-tab", id),
switchTab: (id) => ipcRenderer.invoke("switch-tab", id),
moveTab: (id, targetId, place) => ipcRenderer.invoke("move-tab", id, targetId, place),
suggestAddress: (query, rect) => ipcRenderer.invoke("suggest-address", query, rect),
closeAddressPicker: () => ipcRenderer.invoke("close-address-picker"),
addressCursor: (dir) => ipcRenderer.invoke("address-cursor", dir),
togglePwFill: (rect) => ipcRenderer.invoke("toggle-pw-fill", rect),
onPwAvailability: (cb) => ipcRenderer.on("pw-availability", (_e, d) => cb(d)),
// Update chip: chrome subscribes to update-available; clicking the chip's
// download button opens the URL in the system browser; ✕ dismisses for
// the current session.
onUpdateAvailable: (cb) => ipcRenderer.on("update-available", (_e, d) => cb(d)),
openUpdateDownload: (url) => ipcRenderer.invoke("open-update-download", url),
installUpdateNow: () => ipcRenderer.invoke("install-update-now"),
dismissUpdate: () => ipcRenderer.invoke("dismiss-update"),
goHome: () => ipcRenderer.invoke("go-home"),
back: () => ipcRenderer.invoke("go-back"),
forward: () => ipcRenderer.invoke("go-forward"),
reload: (hard) => ipcRenderer.invoke("reload", !!hard),
stop: () => ipcRenderer.invoke("stop"),
toggleTor: () => ipcRenderer.invoke("toggle-tor"),
openSettings: () => ipcRenderer.invoke("open-settings"),
toggleSiteInfo: (rect) => ipcRenderer.invoke("toggle-site-info", rect),
switchToBcnr: () => ipcRenderer.invoke("switch-to-bcnr"),
setChromeHeight: (h) => ipcRenderer.invoke("set-chrome-height", h),
getSearchEngines: () => ipcRenderer.invoke("search-engines"),
setSearchEngine: (id) => ipcRenderer.invoke("set-search-engine", id),
addEngine: (eng) => ipcRenderer.invoke("add-engine", eng),
removeEngine: (id) => ipcRenderer.invoke("remove-engine", id),
toggleEnginePicker: (rect) => ipcRenderer.invoke("toggle-engine-picker", rect),
onEngines: (cb) => ipcRenderer.on("engines", (_e, d) => cb(d)),
getBookmarks: () => ipcRenderer.invoke("bookmarks-get"),
addBookmark: (bm) => ipcRenderer.invoke("bookmark-add", bm),
updateBookmark: (url, patch) => ipcRenderer.invoke("bookmark-update", url, patch),
removeBookmark: (url) => ipcRenderer.invoke("bookmark-remove", url),
onBookmarks: (cb) => ipcRenderer.on("bookmarks", (_e, d) => cb(d)),
onNav: (cb) => ipcRenderer.on("nav", (_e, d) => cb(d)),
onTor: (cb) => ipcRenderer.on("tor", (_e, d) => cb(d)),
onTabs: (cb) => ipcRenderer.on("tabs", (_e, d) => cb(d)),
tabReload: (id) => ipcRenderer.invoke("tab-reload", id),
tabDuplicate: (id) => ipcRenderer.invoke("tab-duplicate", id),
tabMute: (id, on) => ipcRenderer.invoke("tab-mute", id, on),
tabGroup: (id, color) => ipcRenderer.invoke("tab-group", id, color),
tabGroupToggle: (color) => ipcRenderer.invoke("tab-group-toggle", color),
tabBookmark: (id) => ipcRenderer.invoke("tab-bookmark", id),
onAddressPicked: (cb) => ipcRenderer.on("address-picked", (_e, url) => cb(url)),
onBcnrOffer: (cb) => ipcRenderer.on("bcnr-offer", (_e, d) => cb(d)),
// Collision-mode (BCNR ↔ ICANN) live switcher for the active tab
collisionSwitch: (arg) => ipcRenderer.invoke("collision-switch", arg),
collisionState: () => ipcRenderer.invoke("collision-state"),
// Downloads — the toolbar button subscribes to `downloads` to update its
// badge, and toggleDownloads opens/closes the floating panel.
getDownloads: () => ipcRenderer.invoke("downloads-get"),
toggleDownloads: (rect) => ipcRenderer.invoke("toggle-downloads", rect),
onDownloads: (cb) => ipcRenderer.on("downloads", (_e, d) => cb(d)),
// Add-on sidebar toggle in the toolbar.
toggleSidebar: () => ipcRenderer.invoke("sidebar-toggle"),
sidebarState: () => ipcRenderer.invoke("sidebar-state"),
onSidebarState: (cb) => ipcRenderer.on("sidebar-state", (_e, d) => cb(d)),
});