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 852bb0238..272385d25 100644 --- a/apps/client/src/widgets/NoteDetail.tsx +++ b/apps/client/src/widgets/NoteDetail.tsx @@ -105,9 +105,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 }); } }); 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()); diff --git a/apps/client/src/widgets/type_widgets/Empty.tsx b/apps/client/src/widgets/type_widgets/Empty.tsx index eb0f0c5ee..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,10 +45,9 @@ function NoteSearch() { if (!suggestion?.notePath) { return false; } - - const activeContext = appContext.tabManager.getActiveContext(); - if (activeContext) { - activeContext.setNote(suggestion.notePath); + const activeNoteContext = appContext.tabManager.getNoteContextById(ntxId) ?? appContext.tabManager.getActiveContext(); + if (activeNoteContext) { + activeNoteContext.setNote(suggestion.notePath); } }} /> 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