From 210dcfb98969a9bdda26073c89b7bac352404e3d Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Fri, 28 Nov 2025 19:38:52 +0800 Subject: [PATCH 1/5] fix(empty): open note in the correct split pane --- apps/client/src/widgets/type_widgets/Empty.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/Empty.tsx b/apps/client/src/widgets/type_widgets/Empty.tsx index eb0f0c5ee..013f931fc 100644 --- a/apps/client/src/widgets/type_widgets/Empty.tsx +++ b/apps/client/src/widgets/type_widgets/Empty.tsx @@ -45,10 +45,10 @@ function NoteSearch() { if (!suggestion?.notePath) { return false; } - - const activeContext = appContext.tabManager.getActiveContext(); - if (activeContext) { - activeContext.setNote(suggestion.notePath); + const ntxId = autocompleteRef.current?.closest(".note-split")?.getAttribute("data-ntx-id") ?? null; + const activeNoteContext = appContext.tabManager.getNoteContextById(ntxId) ?? appContext.tabManager.getActiveContext(); + if (activeNoteContext) { + activeNoteContext.setNote(suggestion.notePath); } }} /> From 407cac588a3e60bb185d249c5f9dd2859e7247ac Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Fri, 28 Nov 2025 19:42:04 +0800 Subject: [PATCH 2/5] fix(split): only trigger focusOnDetail when necessary --- apps/client/src/components/app_context.ts | 2 +- apps/client/src/components/tab_manager.ts | 2 +- apps/client/src/widgets/NoteDetail.tsx | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index bda3536da..c44ca01aa 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -487,7 +487,7 @@ type EventMappings = { relationMapResetPanZoom: { ntxId: string | null | undefined }; relationMapResetZoomIn: { ntxId: string | null | undefined }; relationMapResetZoomOut: { ntxId: string | null | undefined }; - activeNoteChanged: {}; + activeNoteChanged: {ntxId: string | null | undefined}; showAddLinkDialog: AddLinkOpts; showIncludeDialog: IncludeNoteOpts; openBulkActionsDialog: { diff --git a/apps/client/src/components/tab_manager.ts b/apps/client/src/components/tab_manager.ts index 127ec30b7..3cf06a779 100644 --- a/apps/client/src/components/tab_manager.ts +++ b/apps/client/src/components/tab_manager.ts @@ -165,7 +165,7 @@ export default class TabManager extends Component { const activeNoteContext = this.getActiveContext(); this.updateDocumentTitle(activeNoteContext); - this.triggerEvent("activeNoteChanged", {}); // trigger this even in on popstate event + this.triggerEvent("activeNoteChanged", {ntxId:activeNoteContext?.ntxId}); // trigger this even in on popstate event } calculateHash(): string { diff --git a/apps/client/src/widgets/NoteDetail.tsx b/apps/client/src/widgets/NoteDetail.tsx index 5b5838584..1ee5c58c6 100644 --- a/apps/client/src/widgets/NoteDetail.tsx +++ b/apps/client/src/widgets/NoteDetail.tsx @@ -95,9 +95,11 @@ export default function NoteDetail() { }); // Automatically focus the editor. - useTriliumEvent("activeNoteChanged", () => { - // Restore focus to the editor when switching tabs, but only if the note tree is not already focused. - if (!document.activeElement?.classList.contains("fancytree-title")) { + useTriliumEvent("activeNoteChanged", ({ ntxId: eventNtxId }) => { + if (eventNtxId != ntxId) return; + // Restore focus to the editor when switching tabs, + // but only if the note tree and the note panel (e.g., note title or note detail) are not focused. + if (!document.activeElement?.classList.contains("fancytree-title") && !parentComponent.$widget[0].closest(".note-split")?.contains(document.activeElement)) { parentComponent.triggerCommand("focusOnDetail", { ntxId }); } }); From 648ab4d736061b49c5033c0392070971f2f22986 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Fri, 28 Nov 2025 19:45:19 +0800 Subject: [PATCH 3/5] fix(left-pane): only focus the note when toggling left pane visibility if necessary --- apps/client/src/widgets/containers/left_pane_container.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/containers/left_pane_container.ts b/apps/client/src/widgets/containers/left_pane_container.ts index e6bc78e23..cd832560b 100644 --- a/apps/client/src/widgets/containers/left_pane_container.ts +++ b/apps/client/src/widgets/containers/left_pane_container.ts @@ -29,7 +29,11 @@ export default class LeftPaneContainer extends FlexContainer { if (visible) { this.triggerEvent("focusTree", {}); } else { - this.triggerEvent("focusOnDetail", { ntxId: appContext.tabManager.getActiveContext()?.ntxId }); + const ntxId = appContext.tabManager.getActiveContext()?.ntxId; + const noteContainer = document.querySelector(`.note-split[data-ntx-id="${ntxId}"]`); + if (!noteContainer?.contains(document.activeElement)) { + this.triggerEvent("focusOnDetail", { ntxId }); + } } options.save("leftPaneVisible", this.currentLeftPaneVisible.toString()); From 1898efa2829c404ce49f045156140c09ca3004b6 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Fri, 28 Nov 2025 19:48:37 +0800 Subject: [PATCH 4/5] chore(e2e): add Playwright tests for split pane --- apps/server-e2e/src/layout/split_pane.spec.ts | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 apps/server-e2e/src/layout/split_pane.spec.ts diff --git a/apps/server-e2e/src/layout/split_pane.spec.ts b/apps/server-e2e/src/layout/split_pane.spec.ts new file mode 100644 index 000000000..82a97b54e --- /dev/null +++ b/apps/server-e2e/src/layout/split_pane.spec.ts @@ -0,0 +1,72 @@ +import { test, expect } from "@playwright/test"; +import App from "../support/app"; + +const TEXT_NOTE_TITLE = "Text notes"; +const CODE_NOTE_TITLE = "Code notes"; + +test("Open the note in the correct split pane", async ({ page, context }) => { + const app = new App(page, context); + await app.goto(); + await app.closeAllTabs(); + + // Open the first split. + await app.goToNoteInNewTab(TEXT_NOTE_TITLE); + const split1 = app.currentNoteSplit; + + // Create a new split. + const splitButton = split1.locator("button.bx-dock-right"); + await expect(splitButton).toBeVisible(); + await splitButton.click(); + + // Search for "Code notes" in the empty area of the second split. + const split2 = app.currentNoteSplit.nth(1);; + await expect(split2).toBeVisible(); + const autocomplete = split2.locator(".note-autocomplete"); + await autocomplete.fill(CODE_NOTE_TITLE); + const resultsSelector = split2.locator(".note-detail-empty-results"); + await expect(resultsSelector).toContainText(CODE_NOTE_TITLE); + + //Focus on the first split. + const noteContent = split1.locator(".note-detail-editable-text-editor"); + await expect(noteContent.locator("p")).toBeVisible(); + await noteContent.focus(); + + // Click the search result in the second split. + await resultsSelector.locator(".aa-suggestion", { hasText: CODE_NOTE_TITLE }) + .nth(1).click(); + + await expect(split2).toContainText(CODE_NOTE_TITLE); +}); + +test("Can directly focus the autocomplete input within the split", async ({ page, context }) => { + const app = new App(page, context); + await app.goto(); + await app.closeAllTabs(); + + // Open the first split. + await app.goToNoteInNewTab(TEXT_NOTE_TITLE); + const split1 = app.currentNoteSplit; + + // Create a new split. + const splitButton = split1.locator("button.bx-dock-right"); + await expect(splitButton).toBeVisible(); + await splitButton.click(); + + // Search for "Code notes" in the empty area of the second split. + const split2 = app.currentNoteSplit.nth(1);; + await expect(split2).toBeVisible(); + + // Focus the first split. + const noteContent = split1.locator(".note-detail-editable-text-editor"); + await expect(noteContent.locator("p")).toBeVisible(); + await noteContent.focus(); + await noteContent.click(); + + // click the autocomplete input box of the second split + const autocomplete = split2.locator(".note-autocomplete"); + await autocomplete.focus(); + await autocomplete.click(); + + await page.waitForTimeout(100); + await expect(autocomplete).toBeFocused(); +}); \ No newline at end of file From 9ae1a5589666b32439c49d43dcf2d990f1c18114 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Sat, 29 Nov 2025 11:38:45 +0800 Subject: [PATCH 5/5] chore(react/empty): obtain ntxId via React props instead of DOM query --- apps/client/src/widgets/type_widgets/Empty.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/Empty.tsx b/apps/client/src/widgets/type_widgets/Empty.tsx index 013f931fc..83a407493 100644 --- a/apps/client/src/widgets/type_widgets/Empty.tsx +++ b/apps/client/src/widgets/type_widgets/Empty.tsx @@ -10,16 +10,16 @@ import FNote from "../../entities/fnote"; import search from "../../services/search"; import { TypeWidgetProps } from "./type_widget"; -export default function Empty({ }: TypeWidgetProps) { +export default function Empty({ ntxId }: TypeWidgetProps) { return ( <> - + ) } -function NoteSearch() { +function NoteSearch({ ntxId }: { ntxId: string | null }) { const resultsContainerRef = useRef(null); const autocompleteRef = useRef(null); @@ -45,7 +45,6 @@ function NoteSearch() { if (!suggestion?.notePath) { return false; } - const ntxId = autocompleteRef.current?.closest(".note-split")?.getAttribute("data-ntx-id") ?? null; const activeNoteContext = appContext.tabManager.getNoteContextById(ntxId) ?? appContext.tabManager.getActiveContext(); if (activeNoteContext) { activeNoteContext.setNote(suggestion.notePath);