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 01/12] 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 02/12] 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 03/12] 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 04/12] 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 05/12] 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); From 5f197107917c1c7e7051f87ef3ec8f7ff5a6349c Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Sat, 29 Nov 2025 22:40:49 +0800 Subject: [PATCH 06/12] fix(insertDateTime): unable to insert date/time via quick editor or shortcut --- .../widgets/type_widgets/text/EditableText.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/text/EditableText.tsx b/apps/client/src/widgets/type_widgets/text/EditableText.tsx index 5e3d06a76..0106ae0c0 100644 --- a/apps/client/src/widgets/type_widgets/text/EditableText.tsx +++ b/apps/client/src/widgets/type_widgets/text/EditableText.tsx @@ -98,6 +98,14 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext editorApi: editorApiRef.current, }); }, + insertDateTimeToTextCommand() { + if (!editorApiRef.current) return; + const date = new Date(); + const customDateTimeFormat = options.get("customDateTimeFormat"); + const dateString = utils.formatDateTime(date, customDateTimeFormat); + + addTextToEditor(dateString); + }, // Include note functionality note addIncludeNoteToTextCommand() { if (!editorApiRef.current) return; @@ -197,14 +205,6 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext }); } - useTriliumEvent("insertDateTimeToText", ({ ntxId: eventNtxId }) => { - if (eventNtxId !== ntxId) return; - const date = new Date(); - const customDateTimeFormat = options.get("customDateTimeFormat"); - const dateString = utils.formatDateTime(date, customDateTimeFormat); - - addTextToEditor(dateString); - }); useTriliumEvent("addTextToActiveEditor", ({ text }) => { if (!noteContext?.isActive()) return; addTextToEditor(text); From ee81037173e592b0edc32555be967c5b3cacf28e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 29 Nov 2025 17:26:17 +0200 Subject: [PATCH 07/12] feat(quick_edit): smooth transition between colors --- apps/client/src/widgets/dialogs/PopupEditor.css | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/dialogs/PopupEditor.css b/apps/client/src/widgets/dialogs/PopupEditor.css index f72cf2274..952e35dc5 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.css +++ b/apps/client/src/widgets/dialogs/PopupEditor.css @@ -19,6 +19,10 @@ body.mobile .modal.popup-editor-dialog .modal-dialog { height: 100%; } +.modal.popup-editor-dialog .modal-content { + transition: background-color 250ms ease-in; +} + .modal.popup-editor-dialog .modal-header .modal-title { font-size: 1.1em; } @@ -58,12 +62,16 @@ body.mobile .modal.popup-editor-dialog .modal-dialog { font-size: 1em; } +.modal.popup-editor-dialog .classic-toolbar-outer-container.visible { + background-color: transparent; +} + .modal.popup-editor-dialog .classic-toolbar-widget { position: sticky; top: 0; inset-inline-start: 0; inset-inline-end: 0; - background: var(--modal-background-color); + background: transparent; z-index: 998; align-items: flex-start; } From 8c1a04c4b28c20627185092bd59665c95b7984d4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 29 Nov 2025 17:32:32 +0200 Subject: [PATCH 08/12] fix(mobile): shortcut keyboard + visible --- apps/client/src/layouts/mobile_layout.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/src/layouts/mobile_layout.tsx b/apps/client/src/layouts/mobile_layout.tsx index e08d48b61..7a177d23b 100644 --- a/apps/client/src/layouts/mobile_layout.tsx +++ b/apps/client/src/layouts/mobile_layout.tsx @@ -32,6 +32,7 @@ import PromotedAttributes from "../widgets/PromotedAttributes.jsx"; const MOBILE_CSS = ` `); registeredClasses.add(className); + if (hue) { + colorsWithHue.add(className); + } } - return `color ${className}`; + return clsx(className, colorsWithHue.has(className) && "with-hue"); } function parseColor(color: string) { diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index 8da51004e..fbb8d1546 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -301,7 +301,7 @@ body .todo-list input[type="checkbox"]:not(:checked):before { border-color: var(--muted-text-color) !important; } -.quick-edit-dialog-wrapper.color { +.quick-edit-dialog-wrapper.with-hue { --modal-background-color: hsl(var(--custom-color-hue), 8.8%, 11.2%); --modal-border-color: hsl(var(--custom-color-hue), 9.4%, 25.1%); --promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 13.2%, 20.8%); diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index a3fc26e93..f736538a8 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -276,7 +276,7 @@ --custom-bg-color: hsl(var(--custom-color-hue), 37%, 89%, 1); } -.quick-edit-dialog-wrapper.color { +.quick-edit-dialog-wrapper.with-hue { --modal-background-color: hsl(var(--custom-color-hue), 56%, 96%); --modal-border-color: hsl(var(--custom-color-hue), 33%, 41%); --promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 40%, 88%);