From 38619258114b1475ea45770f787196a7e403210a Mon Sep 17 00:00:00 2001 From: Local Dev Date: Thu, 8 Oct 2026 00:57:46 +0200 Subject: [PATCH] Theseus: a tab can go back into another window A tab moved to its own window had no way back. Dragging a tab onto another window's tab bar now moves it there, page and all, and the right-click menu lists the other windows under Move to window. A window whose last tab leaves closes. --- main.js | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/main.js b/main.js index 9c040c58..59ee82f7 100644 --- a/main.js +++ b/main.js @@ -5179,6 +5179,37 @@ function moveTabToNewWindow(id, at) { })); return true; } +// Moves a tab into an existing window (dest), page and all, at the end of +// its tab bar, and brings that window forward. A window left without tabs +// closes, like one whose last tab was closed. +function moveTabToWindow(id, dest) { + const t = tabById(id); + if (!t || !t.w || !windows.includes(t.w) || !dest || dest === t.w || !windows.includes(dest)) return false; + const src = t.w; + withWin(src, () => { + if (fsTabId === id) leaveHtmlFullscreen(t, true); + const mine = winTabs(); + const i = mine.indexOf(t); + const next = mine[i + 1] || mine[i - 1]; + try { win.contentView.removeChildView(t.view); } catch {} + t.w = null; // between windows + if (next) { if (activeId === id) setActive(next.id); else emitTabs(); } + }); + t.w = dest; + // After the destination window's last tab in the shared list. + tabs.splice(tabs.indexOf(t), 1); + const there = winTabs(dest); + tabs.splice(there.length ? tabs.indexOf(there[there.length - 1]) + 1 : tabs.length, 0, t); + try { dest.win.contentView.addChildView(t.view); } catch {} + withWin(dest, () => { + if (curW === primaryW && leftPanelKey() && settings.sidebarPanelMode !== "push") raisePanelGrip(); + setActive(id); + layout(); + }); + if (!winTabs(src).length) { try { src.win.close(); } catch {} } + try { dest.win.show(); dest.win.focus(); t.view.webContents.focus(); } catch {} + return true; +} function closeTab(id) { const owner = tabById(id)?.w; if (owner && owner !== curW) return withWin(owner, () => closeTabHere(id)); @@ -5811,6 +5842,16 @@ ipcMain.handle("tab-detach", (e, tabId, x, y) => { const b = win.getBounds(); const inside = px >= b.x && px < b.x + b.width && py >= b.y && py < b.y + b.height; if (inside && py < b.y + CHROME_H + 24) return false; + // Let go over another window's toolbar: the tab joins that window. Only + // when the point is off its own window, which is the one on top while the + // tab is dragged from it. + if (!inside) { + for (const w of windows) { + if (w === curW || !w.win || w.win.isDestroyed() || w.win.isMinimized()) continue; + const wb = w.win.getBounds(); + if (px >= wb.x && px < wb.x + wb.width && py >= wb.y && py < wb.y + (w.chromeH || 84) + 24) return moveTabToWindow(t.id, w); + } + } return moveTabToNewWindow(t.id, { x: px, y: py }); }); ipcMain.handle("tab-context-menu-popup", (e, tabId, rect) => { @@ -5850,6 +5891,12 @@ ipcMain.handle("tab-context-menu-popup", (e, tabId, rect) => { { label: "Reload", click: () => { try { t.view.webContents.reload(); } catch {} } }, { label: "Duplicate", click: () => { if (t.url) createTab(t.url, { after: t.id }); else createTab(null, { after: t.id }); } }, { label: "Move to new window", enabled: winTabs(t.w).length > 1, click: () => moveTabToNewWindow(t.id) }, + ...(windows.length > 1 ? [{ label: "Move to window", submenu: windows.filter((w) => w !== t.w).map((w) => { + const front = tabById(w === curW ? activeId : w.activeId); + const n = winTabs(w).length; + const name = String(front?.title || front?.url || "Window").slice(0, 60); + return { label: n > 1 ? `${name} (and ${n - 1} more)` : name, click: () => moveTabToWindow(t.id, w) }; + }) }] : []), { label: "Group", submenu: groupSubmenu }, { label: "Add to Bookmarks", enabled: !bmDisabled, click: () => { if (bmDisabled) return;