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