mirror of
https://github.com/zadam/trilium.git
synced 2026-09-12 19:50:20 +05:00
fix(search): keep result paths out of the bookmarks clone (closes #10021)
Bookmarking a note clones it under `_lbBookmarks`, so every descendant gains a second note path running through `_hidden`. Searching creates an ad-hoc search note under `_hidden/_search` and activates it. Result cards address their target by bare note id and leave the path to the client, and `getSortedNotePathRecords` compared the active note's path prefix before every other criterion. The real path shares only `root` with the active search note while the bookmark clone shares `root/_hidden`, so two segments beat one and the hidden check below never ran. The card opened the note under Bookmarks, and in grid view the breadcrumb said so too. The tiebreaker comes from #7552, where it picks the nearest clone so that opening a note keeps the user in the subtree they came from. Every candidate there is an ordinary clone, so ranking it below the hoisted, archived, hidden and search comparisons preserves that and only stops it from overruling them. The server was never wrong here: `compareNotePathRecords` ranks hidden paths last and takes no active path. The client threw that answer away and re-derived the path from froca. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7edff12d9b
commit
cd381e09fe
@ -714,6 +714,33 @@ describe("FNote paths & hierarchy", () => {
|
||||
expect(leaf.getBestNotePathString()).toContain("sharedLeaf");
|
||||
});
|
||||
|
||||
it("keeps a bookmark clone out of the best path while a hidden note is active", () => {
|
||||
// Bookmarking clones the note under `_lbBookmarks`, so its children gain a second path
|
||||
// through `_hidden`. A search activates a search note that lives under `_hidden` as well,
|
||||
// and that shared prefix must not promote the bookmark path over the real one.
|
||||
const root = froca.notes["root"] ?? buildNote({ id: "root", title: "root" });
|
||||
const hidden = froca.notes["_hidden"] ?? buildNote({ id: "_hidden", title: "Hidden" });
|
||||
wireChild(root, hidden);
|
||||
|
||||
let launchBarParent = hidden;
|
||||
for (const id of ["_lbRoot", "_lbVisibleLaunchers", "_lbBookmarks"]) {
|
||||
const launcher = buildNote({ id, title: id });
|
||||
wireChild(launchBarParent, launcher);
|
||||
launchBarParent = launcher;
|
||||
}
|
||||
|
||||
const inbox = buildNote({ id: "bmInbox", title: "Inbox" });
|
||||
wireChild(root, inbox);
|
||||
wireChild(launchBarParent, inbox);
|
||||
|
||||
const leaf = buildNote({ id: "bmLeaf", title: "ThisIsTest" });
|
||||
wireChild(inbox, leaf);
|
||||
|
||||
expect(leaf.getAllNotePaths()).toHaveLength(2);
|
||||
expect(leaf.getBestNotePath("root", "root/_hidden/_search/202609/searchNoteId"))
|
||||
.toEqual(["root", "bmInbox", "bmLeaf"]);
|
||||
});
|
||||
|
||||
it("getSortedNotePathRecords orders by archived / hidden / search / length without an active path", () => {
|
||||
const leaf = froca.notes["sharedLeaf"];
|
||||
expect(leaf).toBeDefined();
|
||||
@ -1269,6 +1296,14 @@ function makeBlob(content: string | undefined): FBlob {
|
||||
});
|
||||
}
|
||||
|
||||
/** Wires one froca note as a child of another, the way a branch does in the application. */
|
||||
function wireChild(parent: FNote, child: FNote) {
|
||||
const branchId = `br-${parent.noteId}-${child.noteId}`;
|
||||
registerBranch(branchId, child.noteId, parent.noteId, 0);
|
||||
parent.addChild(child.noteId, branchId, false);
|
||||
child.addParent(parent.noteId, branchId, false);
|
||||
}
|
||||
|
||||
function registerBranch(branchId: string, noteId: string, parentNoteId: string, notePosition: number | undefined) {
|
||||
froca.branches[branchId] = new FBranch(froca, {
|
||||
branchId,
|
||||
|
||||
@ -453,16 +453,6 @@ export default class FNote {
|
||||
};
|
||||
|
||||
notePaths.sort((a, b) => {
|
||||
if (activeNotePath) {
|
||||
const activeSegments = activeNotePath.split('/');
|
||||
const aOverlap = prefixMatchLength(a.notePath, activeSegments);
|
||||
const bOverlap = prefixMatchLength(b.notePath, activeSegments);
|
||||
// Paths with more matching prefix segments are prioritized
|
||||
// when the match count is equal, other criteria are used for sorting
|
||||
if (bOverlap !== aOverlap) {
|
||||
return bOverlap - aOverlap;
|
||||
}
|
||||
}
|
||||
if (a.isInHoistedSubTree !== b.isInHoistedSubTree) {
|
||||
return a.isInHoistedSubTree ? -1 : 1;
|
||||
} else if (a.isArchived !== b.isArchived) {
|
||||
@ -474,6 +464,19 @@ export default class FNote {
|
||||
return a.isSearch ? 1 : -1;
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
if (activeNotePath) {
|
||||
// Among otherwise equal paths, the one sharing the longest prefix with the active
|
||||
// note wins, so opening a clone keeps the user where they came from. The checks
|
||||
// above outrank it: an active `_hidden` note must not promote a bookmark clone.
|
||||
const activeSegments = activeNotePath.split("/");
|
||||
const aOverlap = prefixMatchLength(a.notePath, activeSegments);
|
||||
const bOverlap = prefixMatchLength(b.notePath, activeSegments);
|
||||
if (bOverlap !== aOverlap) {
|
||||
return bOverlap - aOverlap;
|
||||
}
|
||||
}
|
||||
|
||||
return a.notePath.length - b.notePath.length;
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user