Skip to content

Commit ce2d94f

Browse files
authored
Resolve focus issues within split pane (#7877)
2 parents b3c2a1e + 53a8f6b commit ce2d94f

File tree

6 files changed

+90
-13
lines changed

6 files changed

+90
-13
lines changed

apps/client/src/components/app_context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ type EventMappings = {
487487
relationMapResetPanZoom: { ntxId: string | null | undefined };
488488
relationMapResetZoomIn: { ntxId: string | null | undefined };
489489
relationMapResetZoomOut: { ntxId: string | null | undefined };
490-
activeNoteChanged: {};
490+
activeNoteChanged: {ntxId: string | null | undefined};
491491
showAddLinkDialog: AddLinkOpts;
492492
showIncludeDialog: IncludeNoteOpts;
493493
openBulkActionsDialog: {

apps/client/src/components/tab_manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ export default class TabManager extends Component {
165165
const activeNoteContext = this.getActiveContext();
166166
this.updateDocumentTitle(activeNoteContext);
167167

168-
this.triggerEvent("activeNoteChanged", {}); // trigger this even in on popstate event
168+
this.triggerEvent("activeNoteChanged", {ntxId:activeNoteContext?.ntxId}); // trigger this even in on popstate event
169169
}
170170

171171
calculateHash(): string {

apps/client/src/widgets/NoteDetail.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,11 @@ export default function NoteDetail() {
105105
});
106106

107107
// Automatically focus the editor.
108-
useTriliumEvent("activeNoteChanged", () => {
109-
// Restore focus to the editor when switching tabs, but only if the note tree is not already focused.
110-
if (!document.activeElement?.classList.contains("fancytree-title")) {
108+
useTriliumEvent("activeNoteChanged", ({ ntxId: eventNtxId }) => {
109+
if (eventNtxId != ntxId) return;
110+
// Restore focus to the editor when switching tabs,
111+
// but only if the note tree and the note panel (e.g., note title or note detail) are not focused.
112+
if (!document.activeElement?.classList.contains("fancytree-title") && !parentComponent.$widget[0].closest(".note-split")?.contains(document.activeElement)) {
111113
parentComponent.triggerCommand("focusOnDetail", { ntxId });
112114
}
113115
});

apps/client/src/widgets/containers/left_pane_container.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ export default class LeftPaneContainer extends FlexContainer<Component> {
2929
if (visible) {
3030
this.triggerEvent("focusTree", {});
3131
} else {
32-
this.triggerEvent("focusOnDetail", { ntxId: appContext.tabManager.getActiveContext()?.ntxId });
32+
const ntxId = appContext.tabManager.getActiveContext()?.ntxId;
33+
const noteContainer = document.querySelector(`.note-split[data-ntx-id="${ntxId}"]`);
34+
if (!noteContainer?.contains(document.activeElement)) {
35+
this.triggerEvent("focusOnDetail", { ntxId });
36+
}
3337
}
3438

3539
options.save("leftPaneVisible", this.currentLeftPaneVisible.toString());

apps/client/src/widgets/type_widgets/Empty.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ import FNote from "../../entities/fnote";
1010
import search from "../../services/search";
1111
import { TypeWidgetProps } from "./type_widget";
1212

13-
export default function Empty({ }: TypeWidgetProps) {
13+
export default function Empty({ ntxId }: TypeWidgetProps) {
1414
return (
1515
<>
1616
<WorkspaceSwitcher />
17-
<NoteSearch />
17+
<NoteSearch ntxId={ntxId ?? null} />
1818
</>
1919
)
2020
}
2121

22-
function NoteSearch() {
22+
function NoteSearch({ ntxId }: { ntxId: string | null }) {
2323
const resultsContainerRef = useRef<HTMLDivElement>(null);
2424
const autocompleteRef = useRef<HTMLInputElement>(null);
2525

@@ -45,10 +45,9 @@ function NoteSearch() {
4545
if (!suggestion?.notePath) {
4646
return false;
4747
}
48-
49-
const activeContext = appContext.tabManager.getActiveContext();
50-
if (activeContext) {
51-
activeContext.setNote(suggestion.notePath);
48+
const activeNoteContext = appContext.tabManager.getNoteContextById(ntxId) ?? appContext.tabManager.getActiveContext();
49+
if (activeNoteContext) {
50+
activeNoteContext.setNote(suggestion.notePath);
5251
}
5352
}}
5453
/>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { test, expect } from "@playwright/test";
2+
import App from "../support/app";
3+
4+
const TEXT_NOTE_TITLE = "Text notes";
5+
const CODE_NOTE_TITLE = "Code notes";
6+
7+
test("Open the note in the correct split pane", async ({ page, context }) => {
8+
const app = new App(page, context);
9+
await app.goto();
10+
await app.closeAllTabs();
11+
12+
// Open the first split.
13+
await app.goToNoteInNewTab(TEXT_NOTE_TITLE);
14+
const split1 = app.currentNoteSplit;
15+
16+
// Create a new split.
17+
const splitButton = split1.locator("button.bx-dock-right");
18+
await expect(splitButton).toBeVisible();
19+
await splitButton.click();
20+
21+
// Search for "Code notes" in the empty area of the second split.
22+
const split2 = app.currentNoteSplit.nth(1);;
23+
await expect(split2).toBeVisible();
24+
const autocomplete = split2.locator(".note-autocomplete");
25+
await autocomplete.fill(CODE_NOTE_TITLE);
26+
const resultsSelector = split2.locator(".note-detail-empty-results");
27+
await expect(resultsSelector).toContainText(CODE_NOTE_TITLE);
28+
29+
//Focus on the first split.
30+
const noteContent = split1.locator(".note-detail-editable-text-editor");
31+
await expect(noteContent.locator("p")).toBeVisible();
32+
await noteContent.focus();
33+
34+
// Click the search result in the second split.
35+
await resultsSelector.locator(".aa-suggestion", { hasText: CODE_NOTE_TITLE })
36+
.nth(1).click();
37+
38+
await expect(split2).toContainText(CODE_NOTE_TITLE);
39+
});
40+
41+
test("Can directly focus the autocomplete input within the split", async ({ page, context }) => {
42+
const app = new App(page, context);
43+
await app.goto();
44+
await app.closeAllTabs();
45+
46+
// Open the first split.
47+
await app.goToNoteInNewTab(TEXT_NOTE_TITLE);
48+
const split1 = app.currentNoteSplit;
49+
50+
// Create a new split.
51+
const splitButton = split1.locator("button.bx-dock-right");
52+
await expect(splitButton).toBeVisible();
53+
await splitButton.click();
54+
55+
// Search for "Code notes" in the empty area of the second split.
56+
const split2 = app.currentNoteSplit.nth(1);;
57+
await expect(split2).toBeVisible();
58+
59+
// Focus the first split.
60+
const noteContent = split1.locator(".note-detail-editable-text-editor");
61+
await expect(noteContent.locator("p")).toBeVisible();
62+
await noteContent.focus();
63+
await noteContent.click();
64+
65+
// click the autocomplete input box of the second split
66+
const autocomplete = split2.locator(".note-autocomplete");
67+
await autocomplete.focus();
68+
await autocomplete.click();
69+
70+
await page.waitForTimeout(100);
71+
await expect(autocomplete).toBeFocused();
72+
});

0 commit comments

Comments
 (0)