From 8a6b660f77f2b436fede5214933ca366f98c5138 Mon Sep 17 00:00:00 2001 From: PathGao <42336971+PathGao@users.noreply.github.com> Date: Sat, 25 Jul 2026 05:49:25 +0900 Subject: [PATCH 1/2] fix: confirm dirty tabs before batch close --- scripts/batchCloseConfirmation.test.ts | 15 +++++++++++++++ src/lib/MarkdownViewer.svelte | 15 +++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 scripts/batchCloseConfirmation.test.ts diff --git a/scripts/batchCloseConfirmation.test.ts b/scripts/batchCloseConfirmation.test.ts new file mode 100644 index 00000000..cc3b16a7 --- /dev/null +++ b/scripts/batchCloseConfirmation.test.ts @@ -0,0 +1,15 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +const viewer = readFileSync(new URL('../src/lib/MarkdownViewer.svelte', import.meta.url), 'utf8'); + +test('batch tab-close commands use the existing dirty-tab confirmation flow', () => { + assert.match(viewer, /async function closeTabsWithConfirmation\(tabIds: string\[\]\)/); + assert.match( + viewer, + /for \(const tabId of tabIds\) \{\s*if \(!\(await canCloseTab\(tabId\)\)\) return;\s*tabManager\.closeTab\(tabId\);\s*\}/, + ); + assert.match(viewer, /menu-tab-close-others[\s\S]*await closeTabsWithConfirmation\(tabsToClose\)/); + assert.match(viewer, /menu-tab-close-right[\s\S]*await closeTabsWithConfirmation\(tabsToClose\)/); +}); diff --git a/src/lib/MarkdownViewer.svelte b/src/lib/MarkdownViewer.svelte index 25bfce4c..f2a0cd52 100644 --- a/src/lib/MarkdownViewer.svelte +++ b/src/lib/MarkdownViewer.svelte @@ -2073,6 +2073,13 @@ import { t } from './utils/i18n.js'; await destroyWindowAfterTabsClosed(); } + async function closeTabsWithConfirmation(tabIds: string[]) { + for (const tabId of tabIds) { + if (!(await canCloseTab(tabId))) return; + tabManager.closeTab(tabId); + } + } + async function destroyWindowAfterTabsClosed() { if (settings.restoreStateOnReopen) { await persistWindowState(); @@ -2940,19 +2947,19 @@ import { t } from './utils/i18n.js'; }), ); unlisteners.push( - await appWindow.listen('menu-tab-close-others', (event) => { + await appWindow.listen('menu-tab-close-others', async (event) => { const tabId = event.payload as string; const tabsToClose = tabManager.tabs.filter((t) => t.id !== tabId).map((t) => t.id); - tabsToClose.forEach((id) => tabManager.closeTab(id)); + await closeTabsWithConfirmation(tabsToClose); }), ); unlisteners.push( - await appWindow.listen('menu-tab-close-right', (event) => { + await appWindow.listen('menu-tab-close-right', async (event) => { const tabId = event.payload as string; const index = tabManager.tabs.findIndex((t) => t.id === tabId); if (index !== -1) { const tabsToClose = tabManager.tabs.slice(index + 1).map((t) => t.id); - tabsToClose.forEach((id) => tabManager.closeTab(id)); + await closeTabsWithConfirmation(tabsToClose); } }), ); From afd43211576ce71c1294d7420c608471aaac30e8 Mon Sep 17 00:00:00 2001 From: PathGao <42336971+PathGao@users.noreply.github.com> Date: Tue, 28 Jul 2026 03:37:21 +0800 Subject: [PATCH 2/2] test: protect window-targeted batch close --- scripts/batchCloseConfirmation.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/batchCloseConfirmation.test.ts b/scripts/batchCloseConfirmation.test.ts index cc3b16a7..e2e93fa6 100644 --- a/scripts/batchCloseConfirmation.test.ts +++ b/scripts/batchCloseConfirmation.test.ts @@ -12,4 +12,6 @@ test('batch tab-close commands use the existing dirty-tab confirmation flow', () ); assert.match(viewer, /menu-tab-close-others[\s\S]*await closeTabsWithConfirmation\(tabsToClose\)/); assert.match(viewer, /menu-tab-close-right[\s\S]*await closeTabsWithConfirmation\(tabsToClose\)/); + assert.match(viewer, /appWindow\.listen\('menu-tab-close-others', async \(event\)/); + assert.match(viewer, /appWindow\.listen\('menu-tab-close-right', async \(event\)/); });