Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/app/src/App.ai.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ describe("Markra AI workspace", () => {
clipboardImageFolder: "assets",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: false,
closeWindowOnLastTabClose: false,
contentWidth: "default",
contentWidthPx: null,
documentLinksOpen: true,
Expand Down Expand Up @@ -289,6 +290,7 @@ describe("Markra AI workspace", () => {
clipboardImageFolder: "assets",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: false,
closeWindowOnLastTabClose: false,
contentWidth: "default",
contentWidthPx: null,
documentLinksOpen: true,
Expand Down
69 changes: 69 additions & 0 deletions packages/app/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ function createStoredEditorPreferences(
clipboardImageFolder: "assets",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: false,
closeWindowOnLastTabClose: overrides.closeWindowOnLastTabClose ?? false,
contentWidth: "default",
contentWidthPx: null,
documentLinksOpen: true,
Expand Down Expand Up @@ -1613,6 +1614,7 @@ describe("Markra workspace", () => {
clipboardImageFolder: "assets",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: false,
closeWindowOnLastTabClose: false,
contentWidth: "default",
contentWidthPx: null,
documentLinksOpen: true,
Expand Down Expand Up @@ -1686,6 +1688,7 @@ describe("Markra workspace", () => {
clipboardImageFolder: "assets",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: false,
closeWindowOnLastTabClose: false,
contentWidth: "default",
contentWidthPx: null,
documentLinksOpen: true,
Expand Down Expand Up @@ -2962,6 +2965,7 @@ describe("Markra workspace", () => {
clipboardImageFolder: "assets",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: false,
closeWindowOnLastTabClose: false,
contentWidth: "default",
contentWidthPx: null,
documentLinksOpen: true,
Expand Down Expand Up @@ -4215,6 +4219,69 @@ describe("Markra workspace", () => {
filePath: null,
openFilePaths: []
}));
expect(mockedCloseNativeWindow).not.toHaveBeenCalled();
});

it("closes the window from Cmd+W when the last tab closes and the preference is enabled", async () => {
mockedGetStoredEditorPreferences.mockResolvedValue(createStoredEditorPreferences({
closeWindowOnLastTabClose: true
}));
mockOpenMarkdownFile({
content: "# Native file\n\nOpened from disk.",
name: "native.md",
path: mockNativePath
});

renderApp();

fireEvent.keyDown(window, { key: "o", metaKey: true });
await expectVisibleMarkdownText("Native file");

fireEvent.keyDown(window, { key: "w", metaKey: true });

await waitFor(() => expect(mockedCloseNativeWindow).toHaveBeenCalledTimes(1));
});

it("keeps the window open when Cmd+W closes one of multiple tabs", async () => {
mockedGetStoredEditorPreferences.mockResolvedValue(createStoredEditorPreferences({
closeWindowOnLastTabClose: true
}));
mockOpenMarkdownFile({
content: "# Native file\n\nOpened from disk.",
name: "native.md",
path: mockNativePath
});

renderApp();

fireEvent.keyDown(window, { key: "o", metaKey: true });
await expectVisibleMarkdownText("Native file");
fireEvent.click(screen.getByRole("button", { name: "New tab" }));
expect(screen.getAllByRole("tab")).toHaveLength(2);

fireEvent.keyDown(window, { key: "w", metaKey: true });

await waitFor(() => expect(screen.getAllByRole("tab")).toHaveLength(1));
expect(mockedCloseNativeWindow).not.toHaveBeenCalled();
});

it("closes the window when the last tab close button is used", async () => {
mockedGetStoredEditorPreferences.mockResolvedValue(createStoredEditorPreferences({
closeWindowOnLastTabClose: true
}));
mockOpenMarkdownFile({
content: "# Native file\n\nOpened from disk.",
name: "native.md",
path: mockNativePath
});

renderApp();

fireEvent.keyDown(window, { key: "o", metaKey: true });
await expectVisibleMarkdownText("Native file");
fireEvent.click(screen.getByRole("button", { name: "Close tab native.md" }));

await waitFor(() => expect(mockedCloseNativeWindow).toHaveBeenCalledTimes(1));
});

it("previews an image asset from the current folder tree and returns to markdown files", async () => {
Expand Down Expand Up @@ -5992,6 +6059,7 @@ describe("Markra workspace", () => {
clipboardImageFolder: "assets",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: false,
closeWindowOnLastTabClose: false,
contentWidth: "default",
contentWidthPx: null,
documentLinksOpen: true,
Expand Down Expand Up @@ -6164,6 +6232,7 @@ describe("Markra workspace", () => {
clipboardImageFolder: "assets",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: false,
closeWindowOnLastTabClose: false,
contentWidth: "default",
contentWidthPx: null,
documentLinksOpen: true,
Expand Down
14 changes: 14 additions & 0 deletions packages/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,13 @@ function WorkspaceApp() {
path: tab.path
}))
], [documentTabs, imageTabs]);
const closeWindowAfterLastTab = useCallback((closedTabId: string) => {
if (!editorPreferences.preferences.closeWindowOnLastTabClose) return;
// Callers run this only after a successful close; this render still holds the pre-close tab list.
if (titlebarTabs.length !== 1 || titlebarTabs[0]?.id !== closedTabId) return;

closeNativeWindow().catch(() => {});
}, [editorPreferences.preferences.closeWindowOnLastTabClose, titlebarTabs]);
const activeTitlebarTabId = activeImageFile ? imageDocumentTabId(activeImageFile.path) : activeTabId;
const {
clearSideDocumentGroup,
Expand Down Expand Up @@ -2867,13 +2874,15 @@ function WorkspaceApp() {
clearSideDocumentGroup();
updateActiveAiSelection(null);
handleAiCommandClose();
closeWindowAfterLastTab(focusedSideCloseTabId);
return;
}

if (activeImageFile) {
const closingTabId = imageDocumentTabId(activeImageFile.path);
setImageTabs((currentTabs) => currentTabs.filter((tab) => tab.id !== closingTabId));
setActiveImageFile(null);
closeWindowAfterLastTab(closingTabId);
return;
}

Expand All @@ -2886,6 +2895,7 @@ function WorkspaceApp() {
}
updateActiveAiSelection(null);
handleAiCommandClose();
closeWindowAfterLastTab(activeTabId);
return;
}

Expand All @@ -2902,6 +2912,7 @@ function WorkspaceApp() {
clearSideDocumentGroup,
clearOpenDocument,
closeMarkdownTab,
closeWindowAfterLastTab,
confirmCanDiscardCurrentDocument,
documentOperationTarget,
handleAiCommandClose,
Expand Down Expand Up @@ -4378,6 +4389,7 @@ function WorkspaceApp() {
if (closingActiveImage) setActiveImageFile(null);
updateActiveAiSelection(null);
handleAiCommandClose();
closeWindowAfterLastTab(tabId);
return true;
}

Expand All @@ -4389,12 +4401,14 @@ function WorkspaceApp() {
}
updateActiveAiSelection(null);
handleAiCommandClose();
closeWindowAfterLastTab(tabId);
return true;
}, [
activeImageFile,
captureActiveDocumentViewState,
clearSideDocumentGroup,
closeMarkdownTab,
closeWindowAfterLastTab,
handleAiCommandClose,
imageTabs,
sideDocumentGroup,
Expand Down
29 changes: 29 additions & 0 deletions packages/app/src/components/settings/GeneralSettings.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,35 @@ describe("GeneralSettings", () => {
});
});

it("toggles closing the window with the last document tab", () => {
const onUpdatePreferences = vi.fn();

render(
<GeneralSettings
appVersion="0.0.7"
language="en"
preferences={{
...defaultEditorPreferences,
closeWindowOnLastTabClose: false
}}
translate={translate}
welcomeReset={false}
onCheckForUpdates={vi.fn()}
onResetWelcomeDocument={vi.fn()}
onSelectLanguage={vi.fn()}
onUpdatePreferences={onUpdatePreferences}
/>
);

expect(screen.getByRole("heading", { name: "Window" })).toBeInTheDocument();
fireEvent.click(screen.getByRole("switch", { name: "Close window with last tab" }));

expect(onUpdatePreferences).toHaveBeenCalledWith({
...defaultEditorPreferences,
closeWindowOnLastTabClose: true
});
});

it("keeps diagnostics actions out of general settings", () => {
render(
<GeneralSettings
Expand Down
19 changes: 19 additions & 0 deletions packages/app/src/components/settings/GeneralSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,25 @@ export function GeneralSettings({
</p>
) : null}

<SettingsSection label={translate("settings.sections.window")}>
<SettingsRow
title={translate("settings.window.closeWindowOnLastTabClose")}
description={translate("settings.window.closeWindowOnLastTabCloseDescription")}
action={
<SettingsSwitch
checked={preferences.closeWindowOnLastTabClose}
label={translate("settings.window.closeWindowOnLastTabClose")}
onChange={() =>
onUpdatePreferences({
...preferences,
closeWindowOnLastTabClose: !preferences.closeWindowOnLastTabClose
})
}
/>
}
/>
</SettingsSection>

<SettingsSection label={translate("settings.sections.fileOpening")}>
<SettingsRow
title={translate("settings.files.openDroppedFilesInTabs")}
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/hooks/useEditorPreferences.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ vi.mock("../lib/settings/app-settings", () => ({
clipboardImageFolder: "assets",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: false,
closeWindowOnLastTabClose: false,
contentWidth: "default",
contentWidthPx: null,
documentLinksOpen: true,
Expand Down Expand Up @@ -152,6 +153,7 @@ describe("useEditorPreferences", () => {
clipboardImageFolder: "assets",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: false,
closeWindowOnLastTabClose: false,
contentWidth: "default",
contentWidthPx: null,
documentLinksOpen: true,
Expand Down Expand Up @@ -273,6 +275,7 @@ describe("useEditorPreferences", () => {
clipboardImageFolder: "images",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: true,
closeWindowOnLastTabClose: false,
contentWidth: "wide",
contentWidthPx: 1120,
documentLinksOpen: true,
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/lib/settings/app-settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,12 @@ describe("app settings", () => {
expect(normalizeEditorPreferences({ openDroppedFilesInTabs: "yes" }).openDroppedFilesInTabs).toBe(false);
});

it("normalizes the close window on last tab preference", () => {
expect(defaultEditorPreferences.closeWindowOnLastTabClose).toBe(false);
expect(normalizeEditorPreferences({ closeWindowOnLastTabClose: true }).closeWindowOnLastTabClose).toBe(true);
expect(normalizeEditorPreferences({ closeWindowOnLastTabClose: "yes" }).closeWindowOnLastTabClose).toBe(false);
});

it("normalizes the document links visibility preference", () => {
expect(defaultEditorPreferences.documentLinksVisible).toBe(false);
expect(normalizeEditorPreferences({ documentLinksVisible: true }).documentLinksVisible).toBe(true);
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/lib/settings/app-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ export type EditorPreferences = {
bodyFontSize: number;
clipboardImageFolder: string;
closeAiCommandOnAgentPanelOpen: boolean;
closeWindowOnLastTabClose: boolean;
contentWidth: EditorContentWidth;
contentWidthPx: number | null;
copyExternalFilesToStorage: boolean;
Expand Down Expand Up @@ -565,6 +566,7 @@ export const defaultEditorPreferences: EditorPreferences = {
bodyFontSize: 16,
clipboardImageFolder: "assets",
closeAiCommandOnAgentPanelOpen: false,
closeWindowOnLastTabClose: false,
contentWidth: "default",
contentWidthPx: null,
copyExternalFilesToStorage: true,
Expand Down Expand Up @@ -1762,6 +1764,10 @@ export function normalizeEditorPreferences(value: unknown): EditorPreferences {
typeof preferences.closeAiCommandOnAgentPanelOpen === "boolean"
? preferences.closeAiCommandOnAgentPanelOpen
: defaultEditorPreferences.closeAiCommandOnAgentPanelOpen,
closeWindowOnLastTabClose:
typeof preferences.closeWindowOnLastTabClose === "boolean"
? preferences.closeWindowOnLastTabClose
: defaultEditorPreferences.closeWindowOnLastTabClose,
contentWidth: editorContentWidthOptions.includes(preferences.contentWidth as EditorContentWidth)
? (preferences.contentWidth as EditorContentWidth)
: defaultEditorPreferences.contentWidth,
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/lib/settings/editor-preferences.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe("editor preferences", () => {
clipboardImageFolder: "assets",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: false,
closeWindowOnLastTabClose: false,
contentWidth: "default",
contentWidthPx: null,
documentLinksOpen: true,
Expand Down Expand Up @@ -132,6 +133,7 @@ describe("editor preferences", () => {
clipboardImageFolder: "media/screenshots",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: true,
closeWindowOnLastTabClose: false,
contentWidth: "page",
imageUpload: {
fileNamePattern: "web-{name}-{timestamp}",
Expand Down Expand Up @@ -183,6 +185,7 @@ describe("editor preferences", () => {
clipboardImageFolder: "media/screenshots",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: true,
closeWindowOnLastTabClose: false,
contentWidth: "default",
contentWidthPx: null,
documentLinksOpen: true,
Expand Down Expand Up @@ -748,6 +751,7 @@ describe("editor preferences", () => {
clipboardImageFolder: "assets",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: false,
closeWindowOnLastTabClose: false,
contentWidth: "default",
contentWidthPx: null,
documentLinksOpen: true,
Expand Down Expand Up @@ -846,6 +850,7 @@ describe("editor preferences", () => {
clipboardImageFolder: "images",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: true,
closeWindowOnLastTabClose: false,
contentWidth: "wide",
contentWidthPx: 1120,
documentLinksOpen: true,
Expand Down Expand Up @@ -952,6 +957,7 @@ describe("editor preferences", () => {
clipboardImageFolder: "images",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: true,
closeWindowOnLastTabClose: false,
contentWidth: "wide",
contentWidthPx: 1120,
documentLinksOpen: true,
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/lib/settings/settings-events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ describe("settings events", () => {
clipboardImageFolder: "images",
copyExternalFilesToStorage: true,
closeAiCommandOnAgentPanelOpen: true,
closeWindowOnLastTabClose: false,
contentWidth: "wide" as const,
contentWidthPx: 1120,
documentLinksOpen: true,
Expand Down Expand Up @@ -290,6 +291,7 @@ describe("settings events", () => {
payload: {
preferences: {
closeAiCommandOnAgentPanelOpen: false,
closeWindowOnLastTabClose: false,
showAiQuickInputOnSelection: "nope",
showAiSelectionToolbarOnSelection: true
}
Expand Down
Loading
Loading