From d7838f0b6700d56bf2f3a26308e08ba4e5a905db Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Mon, 29 Dec 2025 14:37:35 +0800 Subject: [PATCH] feat(window): restore recently closed windows from tray --- .../tray/closed-windowsTemplate-inverted.png | Bin 0 -> 545 bytes .../closed-windowsTemplate-inverted@1.25x.png | Bin 0 -> 727 bytes .../closed-windowsTemplate-inverted@1.5x.png | Bin 0 -> 828 bytes .../closed-windowsTemplate-inverted@2x.png | Bin 0 -> 931 bytes .../images/tray/closed-windowsTemplate.png | Bin 0 -> 292 bytes .../tray/closed-windowsTemplate@1.25x.png | Bin 0 -> 355 bytes .../tray/closed-windowsTemplate@1.5x.png | Bin 0 -> 434 bytes .../images/tray/closed-windowsTemplate@2x.png | Bin 0 -> 492 bytes .../src/assets/translations/en/server.json | 2 + apps/server/src/services/tray.ts | 44 ++++++++++++++++++ 10 files changed, 46 insertions(+) create mode 100644 apps/desktop/src/assets/images/tray/closed-windowsTemplate-inverted.png create mode 100644 apps/desktop/src/assets/images/tray/closed-windowsTemplate-inverted@1.25x.png create mode 100644 apps/desktop/src/assets/images/tray/closed-windowsTemplate-inverted@1.5x.png create mode 100644 apps/desktop/src/assets/images/tray/closed-windowsTemplate-inverted@2x.png create mode 100644 apps/desktop/src/assets/images/tray/closed-windowsTemplate.png create mode 100644 apps/desktop/src/assets/images/tray/closed-windowsTemplate@1.25x.png create mode 100644 apps/desktop/src/assets/images/tray/closed-windowsTemplate@1.5x.png create mode 100644 apps/desktop/src/assets/images/tray/closed-windowsTemplate@2x.png diff --git a/apps/desktop/src/assets/images/tray/closed-windowsTemplate-inverted.png b/apps/desktop/src/assets/images/tray/closed-windowsTemplate-inverted.png new file mode 100644 index 0000000000000000000000000000000000000000..9e9671407838d9d196efa2b2566aca44bdf600be GIT binary patch literal 545 zcmV++0^a?JP)~K6g@MOm?kg27h(}V=q5ZDCAf6qq9UlP?%ftz5_tXsf6Ehe(M|t@tMWkFmEaFB zAiNZjg8IZb%4E8zW>b~IJFA&{=bU@)y#u@w1OWQ|{^xqV{wa#0m7*vb0O-1Y_s?TO z=yJJyHcj*Ga5#MQeg7)}AP9n|LZQH1*VP=yQ60xoFIB8mDmR5ffla5=?*KpnV6|HP z5<*DVb+h?=UN07l#wCGjwR+DvkJsz>lu{Bx2mqYU z^Z7iyy}iBjJg;XMMn(Yu&iP9ihM}S;Ob8(WV6j*%&NSS3Fin$oyWNk?X47uB+wVG^PR%sUMDVJElSre{u*c)^x6x?y zST2{}obn3rcgK`cFvcLIOh^Edq1v`>91e$_lrm1I(<*>nB0wo+aU5g6-=A$Im&;Mt zb?-gTd$24^tJP}mWHR}=*=!^MfWct!rPXRZ2q6xXQYNL00YC_GD3wYd2q9pMVYk}> j01U$b=N!jB_d5ImpO?StZgo@700000NkvXXu0mjfTi*Au literal 0 HcmV?d00001 diff --git a/apps/desktop/src/assets/images/tray/closed-windowsTemplate-inverted@1.25x.png b/apps/desktop/src/assets/images/tray/closed-windowsTemplate-inverted@1.25x.png new file mode 100644 index 0000000000000000000000000000000000000000..a3ee020b54f650a9edfc05184ac16ec8c421d864 GIT binary patch literal 727 zcmV;|0x127P)NklXLGaL?JP0|K9`up~atV77JZaBD z&tAM}4~39xDS;#`O_6@&LjoJqhS}`yOb=z#jY;g)KMV}Zd%t=A_x1tYbs_hXN~NM( zTU&1#V{JuIh-F#K=kxh7#?r7v&bia=b~}{Pz{$x;CY?@y>;qn2Ugp#3^y^Bca%GyP zJuxxiuh;9pOw)9R0V$=js;bKD?Ckyh{rz`yb8}As0Ax3y*=&B*b-lj0xcK1W;zD0v zUk@K09pxI01|J5Tnwr9~SnN@uQ21sT#t-*<52&i@Bq2o3=kpaI1e~9re zp+1-p0w|?8m&<*bo}N}!Rh=B@A!Dqi>-q=7Fn;=cK5E-`S5Xv#5W>SM6hd&*G=Gne zkI&4{&p#!E1o|qhR;!4q%Jx?+r74*(HDm{KZXi~(Z|qbhX!i4X$LIr#m4NtWe%13&-(&be?ww^{ZM zsaC6*$H&Ke13{%y>1=6fi2(fV&;MYfVn$~UfgprncX#*k#>U1B=NvXSH$yu+I}c^~ zpHX=f#48~LV2nWs0gXn(NF)+(mY0{$R##V_A08fRjx%JLN~O|SI2@)JV~}MT+~K&# zTdAgLFNF{yo6WwsX}@>loI9OP$KjlVZQH;(?>h(~5JEsa9uGw#k!U89c@~L8*5dJa z$UDHE<*~7`;L6HM2xI)65F&BTh37FS7K_Yr90&vg>cPQ5_Vo0$7K_D-gb>Aj48XH` z$z*bEdwYA&aU9FLZjvM^tyZfVjYeO!+il?{yZ@@&1{Zku^#^U4F{UW_xg!7o002ov JPDHLkV1jKwO?dzS literal 0 HcmV?d00001 diff --git a/apps/desktop/src/assets/images/tray/closed-windowsTemplate-inverted@1.5x.png b/apps/desktop/src/assets/images/tray/closed-windowsTemplate-inverted@1.5x.png new file mode 100644 index 0000000000000000000000000000000000000000..f0074a3cb894d9e2fe54cd634edfd585bdc45c1c GIT binary patch literal 828 zcmV-C1H=4@P)i9DwTq1nkB|qzFw~zhG7&LWBE>Enr5j|sT6cwKa57BkLD$}eZ`an= z-U9%5JRX|M+wY?2w_498Xg|LKR!Mlq?AH99F7hS z4t4_o5JK7pCxn2eX(3fry^Kdvh2Ya6952* zZBVP#$}1}?ulD!%KX-L?IV{WK2q6RjAcPQ|5)eYLX`1Jnrj4djsg0H`ibNvbPJuch z5{Y;lTjXfGq?9@dAwUQL%d&12*!H26Ha+R41e|kW2fl5BMjxls*=_4O4(~c(jN$i~ zKnMXCW6*W|Y<+z_dBvD9mYEbR zmzTr3t{-MHnK?yKP|J4x=?v28^a~+`I5;?XXX{$FNC?3RA=uu7c4Ain01yZS+|$$3 zZ+3Tg)2gZlQ>oO`w&WON`H6{%dz~2D?0h~S$!4=(GMUUA0KokG{1eNv>X&0&ELy!@ zH)dvLVv;0%aJgKPWm!#=LI?rpTx@M^WdQ)H)hbUU63=&bc1|$Hu)4bX_2}s6p|0yC zXoXlT*01aOVX0KgmCNOmR^MW=So|(YSN|V`P}@ED_wfr^)N32JYlkcV0000V4m229gzt*!%~ML@8U z`}_Ovi^XELo?+!KK*Sr7aUP9{F$STNciH|%EOijns(_F&R@nobbEw{jO%=d7hvDJj zua#0jL?EREB7#&Zg_dQt`o3R(W8X9d!Lknx4aJCvR##UaE2RJcps%m5rCcummCxs2 z#bPmbRVqG7K!BE(7O-u5nuus|aq)-2!NIRerP4dC_1XCNcpLyCDQKF2ATq-+V19o7 zQSb~al}hi%#>Vc1ZEi&Xz=!Di`}A)eU~O&f zCnBPyrKKO0Qjt0ld6F^4xYio9)(u|-?d|Qx!NEZ;olf6hUS2+6jKRdj#EXkNz*9=G zYQLA(BbUp)Or=uaZES2j!b<=E literal 0 HcmV?d00001 diff --git a/apps/desktop/src/assets/images/tray/closed-windowsTemplate.png b/apps/desktop/src/assets/images/tray/closed-windowsTemplate.png new file mode 100644 index 0000000000000000000000000000000000000000..e3adb5bd560de208419992c8c541c08a76442d6f GIT binary patch literal 292 zcmV+<0o(qGP)0+Kb#lMTA#Oaflu4 zt^gck5;b!RZ@9vH2JPT0%c=IGu-C^yBZA`yYz7@(e&}E;a>v384Dqz0?K%x*?Y0Vw znO)`_F#QAIG6$@qw6uRN0VG_YizA#_Ob6>-U>Enelf9D?3vIK}O|ZQjeEg+2p%;Jd q;8a+seDrV==VURJPn;BVS*~xC+u@k(3jS>jhS}S& z!?KYWn|v@ZXV1)kXU@)923xOe_tt@KK1Ru_K}41t@GNtTYr<@&bsH za8Ze7qF{0Mki*XpbB3|e)tuYfYoERMx$v)JDC^(BD(2Bd2c!6jUjg7*Q>@TKZt z1MjgC^SAH~H^aCWj-!2rMLez&e#DMjn2ouI7%32k`(Zm8T3|RMbkLAe^(f|^hCvCI zFc})1Izk&W(I2X`CFpWzLhFLSZ|vhhimY4crt^_rhVDTa!?+ZQW=8mq=lB@&C)oy0 z3mgoGlLuIc^0(PV)WeYD{ar>`ZgD2AId((@!hr4}>x1^oF#wW~O zMLxnrAL0#c<7JHJq)2Y(r0R#WiMLV#PijO@v76aW;d8b=fv>XXy6(&DhS{ibaEyDk c@%*>=1A9R|raB6-`2YX_07*qoM6N<$f=80b3jhEB literal 0 HcmV?d00001 diff --git a/apps/desktop/src/assets/images/tray/closed-windowsTemplate@2x.png b/apps/desktop/src/assets/images/tray/closed-windowsTemplate@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..30f5b476bd11ec2330b90ca671b6483f457a4b3e GIT binary patch literal 492 zcmV1no zgLw9%e^OR)gcUr&0MDz8zhkRKkfv-!j0r1=!O-+Acknv;*RU%|UqfDmFX4Mwo=mr3 zTr#+d8_~ann=OLFb)1X-Fk)1aVWVZBCTv8E326r47Cyz?Q7q_1t5sh?Prh}(!uQN% znJhKvh&1jo0o(H8?8kEvt?;$2!4~YxQ{5)rRTRGV(~0ymq}`|1lo{Xi1awIzFUG$n z;Jjqpmo4A}-qaan*j$Q~VA&a1ih#8_V*}Ikpi=>B_=3Bou~odoQ#HSU1&gSG;zo5F z7qEwKSZ}g;QAKzMKk*eGaiz+zt0LURAui)BJ~tIGUVx~xrJ&F7!10=>CByyb|FK}^ zFu;5Hy{|}nQ4^zi3)0i@2QHSxv=Oi;zjzdBe`%W4Ug|e}%T4*U{IThEyzW>&#H(a% iCdf%>^T*_r|C`^pV{U1Mlf)eW0000 !openedWindowIds.includes(win.windowId)) + .sort((a, b) => { + // If closedAt is null, it indicates an abnormal closure and should be placed at the end + if (a.closedAt === null && b.closedAt === null) return 0; + if (a.closedAt === null) return 1; + if (b.closedAt === null) return -1; + // Otherwise, sort by time in descending order + return b.closedAt - a.closedAt; + }); + const menuItems: Electron.MenuItemConstructorOptions[] = []; + for (const win of closedWindows) { + const activeCtx = win.contexts.find(c => c.active === true); + const activateNotePath = (activeCtx ?? win.contexts[0])?.notePath; + const activateNoteId = activateNotePath?.split("/").pop() ?? null; + + // Get the title of the closed window + const rawTitle = activateNoteId ? becca_service.getNoteTitle(activateNoteId) : ""; + let winTitle = rawTitle.length > 20 ? `${rawTitle.slice(0, 17)}...` : rawTitle; + const mainTabCount = win.contexts.filter(ctx => ctx.mainNtxId === null).length; + if (mainTabCount > 1) { + const tabSuffix = t("tray.tabs-total", { number: mainTabCount }); + winTitle += ` (${tabSuffix})`; + } + + menuItems.push({ + label: winTitle, + type: "normal", + click: () => win.windowId !== "main" ? windowService.createExtraWindow(win.windowId, "") : windowService.createMainWindow() + }); + } + + return menuItems; + } + const windowVisibilityMenuItems: Electron.MenuItemConstructorOptions[] = []; // Only call getWindowTitle if windowVisibilityMap has more than one window @@ -258,6 +296,12 @@ function updateTrayMenu() { icon: getIconPath("recents"), submenu: buildRecentNotesMenu() }, + { + label: t("tray.recently-closed-windows"), + type: "submenu", + icon: getIconPath("closed-windows"), + submenu: buildClosedWindowsMenu() + }, { type: "separator" }, { label: t("tray.close"),