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/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 117eeb8b7..fbb8d1546 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -98,6 +98,7 @@ --menu-item-delimiter-color: #ffffff1c; --menu-item-group-header-color: #ffffff91; --menu-section-background-color: #fefefe08; + --menu-submenu-mobile-background-color: rgba(0, 0, 0, 0.15); --modal-backdrop-color: #000; --modal-shadow-color: rgba(0, 0, 0, .5); @@ -300,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%); diff --git a/apps/client/src/stylesheets/theme-next/base.css b/apps/client/src/stylesheets/theme-next/base.css index 5d5bd2387..0902a9aa0 100644 --- a/apps/client/src/stylesheets/theme-next/base.css +++ b/apps/client/src/stylesheets/theme-next/base.css @@ -62,6 +62,7 @@ --menu-padding-size: 8px; --menu-item-icon-vert-offset: -2px; + --menu-submenu-mobile-background-color: rgba(255, 255, 255, 0.15); --more-accented-background-color: var(--card-background-hover-color); @@ -384,30 +385,16 @@ body.mobile .dropdown-menu { } .dropdown-menu { - --menu-background-color: rgba(0, 0, 0, 0.15); + --menu-background-color: --menu-submenu-mobile-background-color; + --bs-dropdown-divider-margin-y: 0.25rem; border-radius: 0; max-height: 0; transition: max-height 100ms ease-in; - display: block !important; - + display: block !important; + &.show { max-height: 1000px; - } - - .dropdown-item { - background: transparent; - } - } - - .dropdown-divider { - visibility: visible; - margin: 0; - height: 3px; - border-top: unset; - background-color: rgba(0, 0, 0, 0.2); - - &:after { - content: unset; + padding: 0.5rem 0.75rem !important; } } 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/dialogs/PopupEditor.css b/apps/client/src/widgets/dialogs/PopupEditor.css index f72cf2274..80e8b3e70 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.css +++ b/apps/client/src/widgets/dialogs/PopupEditor.css @@ -14,11 +14,15 @@ body.desktop .modal.popup-editor-dialog .modal-dialog { } body.mobile .modal.popup-editor-dialog .modal-dialog { - max-width: 90vw; + max-width: min(var(--preferred-max-content-width), 95vw); max-height: var(--tn-modal-max-height); 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; } 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/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); 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