From 7740154bdce50b38c5d74c7ad7d2aeb608701212 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Mon, 29 Dec 2025 14:32:53 +0800 Subject: [PATCH] feat(window): add windowId for extra windows --- apps/client/src/components/app_context.ts | 7 +-- apps/client/src/components/entrypoints.ts | 5 +- apps/client/src/types.d.ts | 1 + apps/desktop/src/main.ts | 5 +- .../assets/views/partials/windowGlobal.ejs | 1 + apps/server/src/routes/index.ts | 1 + apps/server/src/services/window.ts | 54 +++++++++++++------ 7 files changed, 53 insertions(+), 21 deletions(-) diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index e0b8c651b..aba2920d8 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -535,6 +535,7 @@ export type FilteredCommandNames = keyof Pick | (() => boolean))[]; tabManager!: TabManager; @@ -543,10 +544,11 @@ export class AppContext extends Component { lastSearchString?: string; - constructor(isMainWindow: boolean) { + constructor(isMainWindow: boolean, windowId: string) { super(); this.isMainWindow = isMainWindow; + this.windowId = windowId; // non-widget/layout components needed for the application this.components = []; this.beforeUnloadListeners = []; @@ -676,8 +678,7 @@ export class AppContext extends Component { this.beforeUnloadListeners = this.beforeUnloadListeners.filter(l => l !== listener); } } - -const appContext = new AppContext(window.glob.isMainWindow); +const appContext = new AppContext(window.glob.isMainWindow, window.glob.windowId); // we should save all outstanding changes before the page/app is closed $(window).on("beforeunload", () => { diff --git a/apps/client/src/components/entrypoints.ts b/apps/client/src/components/entrypoints.ts index 8a902666f..4c67dce2a 100644 --- a/apps/client/src/components/entrypoints.ts +++ b/apps/client/src/components/entrypoints.ts @@ -142,14 +142,15 @@ export default class Entrypoints extends Component { } async openInWindowCommand({ notePath, hoistedNoteId, viewScope }: NoteCommandData) { + const extraWindowId = utils.randomString(4); const extraWindowHash = linkService.calculateHash({ notePath, hoistedNoteId, viewScope }); if (utils.isElectron()) { const { ipcRenderer } = utils.dynamicRequire("electron"); - ipcRenderer.send("create-extra-window", { extraWindowHash }); + ipcRenderer.send("create-extra-window", { extraWindowId, extraWindowHash }); } else { - const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extraWindow=1${extraWindowHash}`; + const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extraWindow=${extraWindowId}${extraWindowHash}`; window.open(url, "", "width=1000,height=800"); } diff --git a/apps/client/src/types.d.ts b/apps/client/src/types.d.ts index 7128ea5d8..34cd9a8fe 100644 --- a/apps/client/src/types.d.ts +++ b/apps/client/src/types.d.ts @@ -34,6 +34,7 @@ interface CustomGlobals { isProtectedSessionAvailable: boolean; isDev: boolean; isMainWindow: boolean; + windowId: string; maxEntityChangeIdAtLoad: number; maxEntityChangeSyncIdAtLoad: number; assetPath: string; diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 3617c4c9f..ecbbd2024 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -6,6 +6,8 @@ import sqlInit from "@triliumnext/server/src/services/sql_init.js"; import windowService from "@triliumnext/server/src/services/window.js"; import tray from "@triliumnext/server/src/services/tray.js"; import options from "@triliumnext/server/src/services/options.js"; +import { randomString } from "@triliumnext/server/src/services/utils.js"; + import electronDebug from "electron-debug"; import electronDl from "electron-dl"; import { PRODUCT_NAME } from "./app-info"; @@ -72,7 +74,8 @@ async function main() { app.on("second-instance", (event, commandLine) => { const lastFocusedWindow = windowService.getLastFocusedWindow(); if (commandLine.includes("--new-window")) { - windowService.createExtraWindow(""); + const extraWindowId = randomString(4); + windowService.createExtraWindow(extraWindowId, ""); } else if (lastFocusedWindow) { if (lastFocusedWindow.isMinimized()) { lastFocusedWindow.restore(); diff --git a/apps/server/src/assets/views/partials/windowGlobal.ejs b/apps/server/src/assets/views/partials/windowGlobal.ejs index c69351e1a..382990712 100644 --- a/apps/server/src/assets/views/partials/windowGlobal.ejs +++ b/apps/server/src/assets/views/partials/windowGlobal.ejs @@ -12,6 +12,7 @@ isDev: <%= isDev %>, appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %>, isMainWindow: <%= isMainWindow %>, + windowId: "<%= windowId %>", isProtectedSessionAvailable: <%= isProtectedSessionAvailable %>, triliumVersion: "<%= triliumVersion %>", assetPath: "<%= assetPath %>", diff --git a/apps/server/src/routes/index.ts b/apps/server/src/routes/index.ts index cde9885f0..4491764f3 100644 --- a/apps/server/src/routes/index.ts +++ b/apps/server/src/routes/index.ts @@ -56,6 +56,7 @@ function index(req: Request, res: Response) { appCssNoteIds: getAppCssNoteIds(), isDev, isMainWindow: view === "mobile" ? true : !req.query.extraWindow, + windowId: view !== "mobile" && req.query.extraWindow ? req.query.extraWindow : "main", isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(), triliumVersion: packageJson.version, assetPath, diff --git a/apps/server/src/services/window.ts b/apps/server/src/services/window.ts index 4431226ab..7670b310d 100644 --- a/apps/server/src/services/window.ts +++ b/apps/server/src/services/window.ts @@ -16,28 +16,47 @@ import { RESOURCE_DIR } from "./resource_dir.js"; // Prevent the window being garbage collected let mainWindow: BrowserWindow | null; let setupWindow: BrowserWindow | null; -let allWindows: BrowserWindow[] = []; // // Used to store all windows, sorted by the order of focus. -function trackWindowFocus(win: BrowserWindow) { +interface WindowEntry { + window: BrowserWindow; + windowId: string; // custom window ID +} +let allWindowEntries: WindowEntry[] = []; + +function trackWindowFocus(win: BrowserWindow, windowId: string) { // We need to get the last focused window from allWindows. If the last window is closed, we return the previous window. // Therefore, we need to push the window into the allWindows array every time it gets focused. win.on("focus", () => { - allWindows = allWindows.filter(w => !w.isDestroyed() && w !== win); - allWindows.push(win); + allWindowEntries = allWindowEntries.filter(w => !w.window.isDestroyed() && w.window !== win); + allWindowEntries.push({ window: win, windowId: windowId }); + if (!optionService.getOptionBool("disableTray")) { electron.ipcMain.emit("reload-tray"); } }); win.on("closed", () => { - allWindows = allWindows.filter(w => !w.isDestroyed()); + cls.wrap(() => { + const savedWindows = JSON.parse( + optionService.getOption("openNoteContexts") || "[]" + ); + + const win = savedWindows.find(w => w.windowId === windowId); + if (win) { + win.closedAt = Date.now(); + } + + optionService.setOption("openNoteContexts", JSON.stringify(savedWindows)); + })(); + + allWindowEntries = allWindowEntries.filter(w => !w.window.isDestroyed()); if (!optionService.getOptionBool("disableTray")) { electron.ipcMain.emit("reload-tray"); } }); } -async function createExtraWindow(extraWindowHash: string) { +async function createExtraWindow(extraWindowId: string, extraWindowHash: string) { const spellcheckEnabled = optionService.getOptionBool("spellCheckEnabled"); const { BrowserWindow } = await import("electron"); @@ -56,15 +75,15 @@ async function createExtraWindow(extraWindowHash: string) { }); win.setMenuBarVisibility(false); - win.loadURL(`http://127.0.0.1:${port}/?extraWindow=1${extraWindowHash}`); + win.loadURL(`http://127.0.0.1:${port}/?extraWindow=${extraWindowId}${extraWindowHash}`); configureWebContents(win.webContents, spellcheckEnabled); - trackWindowFocus(win); + trackWindowFocus(win, extraWindowId); } electron.ipcMain.on("create-extra-window", (event, arg) => { - createExtraWindow(arg.extraWindowHash); + createExtraWindow(arg.extraWindowId, arg.extraWindowHash); }); interface PrintOpts { @@ -168,8 +187,8 @@ async function getBrowserWindowForPrinting(e: IpcMainEvent, notePath: string, ac return browserWindow; } -async function createMainWindow(app: App) { - if ("setUserTasks" in app) { +async function createMainWindow(app?: App) { + if (app && "setUserTasks" in app) { app.setUserTasks([ { program: process.execPath, @@ -219,7 +238,7 @@ async function createMainWindow(app: App) { mainWindow.on("closed", () => (mainWindow = null)); configureWebContents(mainWindow.webContents, spellcheckEnabled); - trackWindowFocus(mainWindow); + trackWindowFocus(mainWindow, "main"); } function getWindowExtraOpts() { @@ -381,11 +400,15 @@ function getMainWindow() { } function getLastFocusedWindow() { - return allWindows.length > 0 ? allWindows[allWindows.length - 1] : null; + return allWindowEntries.length > 0 ? allWindowEntries[allWindowEntries.length - 1]?.window : null; } function getAllWindows() { - return allWindows; + return allWindowEntries.map(e => e.window); +} + +function getAllWindowIds(): string[] { + return allWindowEntries.map(e => e.windowId); } export default { @@ -396,5 +419,6 @@ export default { registerGlobalShortcuts, getMainWindow, getLastFocusedWindow, - getAllWindows + getAllWindows, + getAllWindowIds };