Merge remote-tracking branch 'origin/develop' into ai-llm-integration

This commit is contained in:
Elian Doran 2025-04-17 22:24:55 +03:00
commit 0133e83d23
No known key found for this signature in database
24 changed files with 264 additions and 52 deletions

View File

@ -5,6 +5,7 @@
"vitest.explorer",
"ms-playwright.playwright",
"tobermory.es6-string-html",
"dbaeumer.vscode-eslint"
"dbaeumer.vscode-eslint",
"yzhang.markdown-all-in-one"
]
}

View File

@ -25,6 +25,11 @@ keyPath=
# expressjs shortcuts are supported: loopback(127.0.0.1/8, ::1/128), linklocal(169.254.0.0/16, fe80::/10), uniquelocal(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7)
trustedReverseProxy=false
# setting the CORS headers for cross-origin requests
# corsAllowOrigin='*'
# corsAllowMethods='GET,POST,PUT,DELETE,PATCH'
# corsAllowHeaders='Content-Type,Authorization'
[Session]
# Use this setting to set a custom value for the "Max-Age" Attribute of the session cookie.

View File

@ -1,7 +1,4 @@
# v0.93.0
## 💡 Key highlights
* …
## 🐞 Bugfixes
@ -15,6 +12,10 @@
* [config.Session.cookieMaxAge is ignored](https://github.com/TriliumNext/Notes/issues/1709) by @pano9000
* [Return correct HTTP status code on failed login attempts instead of 200](https://github.com/TriliumNext/Notes/issues/1707) by @pano9000
* [Calendar stops displaying notes after adding a Day Note](https://github.com/TriliumNext/Notes/issues/1705)
* Full anonymization not redacting attachment titles.
* Unable to trigger "Move to" dialog via keyboard shortcut.
* [Note ordering doesn't load correctly, only shows up right after moving a note](https://github.com/TriliumNext/Notes/issues/1727)
* [Note selection dialog shows icon class when selecting result with arrow button (jump to note / create link)](https://github.com/TriliumNext/Notes/issues/1721)
## ✨ Improvements
@ -23,6 +24,7 @@
* Reduce extra whitespace between list items.
* Preserve include note.
* Handle note titles that contain inline code.
* Support to-do lists.
* In-app help:
* Document structure is now precalculated, so start-up time should be slightly better.
* Optimized the content in order to reduce the size on disk.
@ -35,10 +37,8 @@
* Basic Touch Bar support for macOS.
* [Support Bearer Token](https://github.com/TriliumNext/Notes/issues/1701)
* The tab bar is now scrollable when there are many tabs by @SiriusXT
## 🌍 Internationalization
* …
* Make each part of the note path clickable by @SiriusXT
* Allow setting CORS headers by @yiranlus
## 📖 Documentation
@ -47,6 +47,5 @@
## 🛠️ Technical updates
* upgrade to express 5.1.0 by @pano9000
* update dependency mind-elixir to v4.5.1
* remove non-working cookiePath option by @pano9000

View File

@ -9968,6 +9968,26 @@
"format": "markdown",
"dataFileName": "Trilium instance.md",
"attachments": []
},
{
"isClone": false,
"noteId": "LWtBjFej3wX3",
"notePath": [
"pOsGYCXsbNQG",
"tC7s2alapj8V",
"Gzjqa934BdH4",
"LWtBjFej3wX3"
],
"title": "Cross-Origin Resource Sharing (CORS)",
"notePosition": 20,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [],
"format": "markdown",
"dataFileName": "Cross-Origin Resource Sharing .md",
"attachments": []
}
]
},

View File

@ -0,0 +1,6 @@
# Cross-Origin Resource Sharing (CORS)
By default, Trilium cannot be accessed in web browsers by requests coming from other domains/origins than Trilium itself. 
However, it is possible to manually configure [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) since Trilium v0.93.0 using environment variables or `config.ini`, as follows:
<figure class="table" style="width:100%;"><table class="ck-table-resized"><colgroup><col style="width:26.93%;"><col style="width:32.46%;"><col style="width:40.61%;"></colgroup><thead><tr><th>CORS Header</th><th>Corresponding option in <code>config.ini</code></th><th>Corresponding option in environment variables in the <code>Network</code> section</th></tr></thead><tbody><tr><td><code>Access-Control-Allow-Origin</code></td><td><code>TRILIUM_NETWORK_CORS_ALLOW_ORIGIN</code></td><td><code>corsAllowOrigin</code>&nbsp;</td></tr><tr><td><code>Access-Control-Allow-Methods</code></td><td><code>TRILIUM_NETWORK_CORS_ALLOW_METHODS</code></td><td><code>corsAllowMethods</code>&nbsp;</td></tr><tr><td><code>Access-Control-Allow-Headers</code></td><td><code>TRILIUM_NETWORK_CORS_ALLOW_HEADERS</code></td><td><code>corsAllowHeaders</code></td></tr></tbody></table></figure>

20
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "trilium",
"version": "0.92.7",
"version": "0.93.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "trilium",
"version": "0.92.7",
"version": "0.93.0",
"license": "AGPL-3.0-only",
"dependencies": {
"@anthropic-ai/sdk": "0.39.0",
@ -50,7 +50,7 @@
"html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.6",
"i18next": "24.2.3",
"i18next": "25.0.0",
"i18next-fs-backend": "2.6.0",
"image-type": "5.2.0",
"ini": "5.0.0",
@ -73,7 +73,7 @@
"rand-token": "1.0.1",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",
"sanitize-html": "2.15.0",
"sanitize-html": "2.16.0",
"sax": "1.4.1",
"serve-favicon": "2.5.0",
"session-file-store": "1.5.0",
@ -12742,9 +12742,9 @@
}
},
"node_modules/i18next": {
"version": "24.2.3",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.3.tgz",
"integrity": "sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A==",
"version": "25.0.0",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.0.0.tgz",
"integrity": "sha512-POPvwjOPR1GQvRnbikTMPEhQD+ekd186MHE6NtVxl3Lby+gPp0iq60eCqGrY6wfRnp1lejjFNu0EKs1afA322w==",
"funding": [
{
"type": "individual",
@ -18324,9 +18324,9 @@
}
},
"node_modules/sanitize-html": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.15.0.tgz",
"integrity": "sha512-wIjst57vJGpLyBP8ioUbg6ThwJie5SuSIjHxJg53v5Fg+kUK+AXlb7bK3RNXpp315MvwM+0OBGCV6h5pPHsVhA==",
"version": "2.16.0",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.16.0.tgz",
"integrity": "sha512-0s4caLuHHaZFVxFTG74oW91+j6vW7gKbGD6CD2+miP73CE6z6YtOBN0ArtLd2UGyi4IC7K47v3ENUbQX4jV3Mg==",
"license": "MIT",
"dependencies": {
"deepmerge": "^4.2.2",

View File

@ -2,7 +2,7 @@
"name": "trilium",
"productName": "TriliumNext Notes",
"description": "Build your personal knowledge base with TriliumNext Notes",
"version": "0.92.7",
"version": "0.93.0",
"license": "AGPL-3.0-only",
"main": "./electron-main.js",
"author": {
@ -110,7 +110,7 @@
"html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.6",
"i18next": "24.2.3",
"i18next": "25.0.0",
"i18next-fs-backend": "2.6.0",
"image-type": "5.2.0",
"ini": "5.0.0",
@ -133,7 +133,7 @@
"rand-token": "1.0.1",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",
"sanitize-html": "2.15.0",
"sanitize-html": "2.16.0",
"sax": "1.4.1",
"serve-favicon": "2.5.0",
"session-file-store": "1.5.0",

View File

@ -7,6 +7,7 @@ import compression from "compression";
import { fileURLToPath } from "url";
import { dirname } from "path";
import sessionParser from "./routes/session_parser.js";
import config from "./services/config.js";
import utils from "./services/utils.js";
import assets from "./routes/assets.js";
import routes from "./routes/routes.js";
@ -71,6 +72,17 @@ app.set("views", path.join(scriptDir, "views"));
app.set("view engine", "ejs");
app.use((req, res, next) => {
// set CORS header
if (config["Network"]["corsAllowOrigin"]) {
res.header("Access-Control-Allow-Origin", config["Network"]["corsAllowOrigin"]);
}
if (config["Network"]["corsAllowMethods"]) {
res.header("Access-Control-Allow-Methods", config["Network"]["corsAllowMethods"]);
}
if (config["Network"]["corsAllowHeaders"]) {
res.header("Access-Control-Allow-Headers", config["Network"]["corsAllowHeaders"]);
}
res.locals.t = t;
return next();
});

View File

@ -53,8 +53,8 @@ export interface ContextMenuCommandData extends CommandData {
node: Fancytree.FancytreeNode;
notePath?: string;
noteId?: string;
selectedOrActiveBranchIds?: any; // TODO: Remove any once type is defined
selectedOrActiveNoteIds: any; // TODO: Remove any once type is defined
selectedOrActiveBranchIds: string[];
selectedOrActiveNoteIds?: string[];
}
export interface NoteCommandData extends CommandData {

View File

@ -18,10 +18,26 @@ export default class MainTreeExecutors extends Component {
}
async cloneNotesToCommand({ selectedOrActiveNoteIds }: EventData<"cloneNotesTo">) {
if (!selectedOrActiveNoteIds && this.tree) {
selectedOrActiveNoteIds = this.tree.getSelectedOrActiveNodes().map((node) => node.data.noteId);
}
if (!selectedOrActiveNoteIds) {
return;
}
this.triggerCommand("cloneNoteIdsTo", { noteIds: selectedOrActiveNoteIds });
}
async moveNotesToCommand({ selectedOrActiveBranchIds }: EventData<"moveNotesTo">) {
if (!selectedOrActiveBranchIds && this.tree) {
selectedOrActiveBranchIds = this.tree.getSelectedOrActiveNodes().map((node) => node.data.branchId);
}
if (!selectedOrActiveBranchIds) {
return;
}
this.triggerCommand("moveBranchIdsTo", { branchIds: selectedOrActiveBranchIds });
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,46 @@
<p>By default, Trilium cannot be accessed in web browsers by requests coming
from other domains/origins than Trilium itself.&nbsp;</p>
<p>However, it is possible to manually configure <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS">Cross-Origin Resource Sharing (CORS)</a> since
Trilium v0.93.0 using environment variables or <code>config.ini</code>,
as follows:</p>
<figure class="table" style="width:100%;">
<table class="ck-table-resized">
<colgroup>
<col style="width:26.93%;">
<col style="width:32.46%;">
<col style="width:40.61%;">
</colgroup>
<thead>
<tr>
<th>CORS Header</th>
<th>Corresponding option in <code>config.ini</code>
</th>
<th>Corresponding option in environment variables in the <code>Network</code> section</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Access-Control-Allow-Origin</code>
</td>
<td><code>TRILIUM_NETWORK_CORS_ALLOW_ORIGIN</code>
</td>
<td><code>corsAllowOrigin</code>&nbsp;</td>
</tr>
<tr>
<td><code>Access-Control-Allow-Methods</code>
</td>
<td><code>TRILIUM_NETWORK_CORS_ALLOW_METHODS</code>
</td>
<td><code>corsAllowMethods</code>&nbsp;</td>
</tr>
<tr>
<td><code>Access-Control-Allow-Headers</code>
</td>
<td><code>TRILIUM_NETWORK_CORS_ALLOW_HEADERS</code>
</td>
<td><code>corsAllowHeaders</code>
</td>
</tr>
</tbody>
</table>
</figure>

View File

@ -152,7 +152,7 @@ class FNote {
for (const branchId of Object.values(this.childToBranch)) {
const notePosition = this.froca.getBranch(branchId)?.notePosition;
if (notePosition) {
if (notePosition !== undefined) {
branchIdPos[branchId] = notePosition;
}
}

View File

@ -278,15 +278,20 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
const { notePath, viewScope } = parseNavigationStateFromUrl(hrefLink);
const ctrlKey = utils.isCtrlKey(evt);
const shiftKey = evt.shiftKey;
const isLeftClick = "which" in evt && evt.which === 1;
const isMiddleClick = "which" in evt && evt.which === 2;
const targetIsBlank = ($link?.attr("target") === "_blank");
const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick || targetIsBlank;
const activate = (isLeftClick && ctrlKey && shiftKey) || (isMiddleClick && shiftKey);
const openInNewWindow = isLeftClick && evt.shiftKey && !ctrlKey;
if (notePath) {
if (openInNewTab) {
if (openInNewWindow) {
appContext.triggerCommand("openInWindow", { notePath, viewScope });
} else if (openInNewTab) {
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {
activate: targetIsBlank,
activate: activate ? true : targetIsBlank,
viewScope
});
} else if (isLeftClick) {

View File

@ -21,6 +21,7 @@ function getSearchDelay(notesCount: number): number {
}
let searchDelay = getSearchDelay(notesCount);
// TODO: Deduplicate with server.
export interface Suggestion {
noteTitle?: string;
externalLink?: string;
@ -29,6 +30,7 @@ export interface Suggestion {
highlightedNotePathTitle?: string;
action?: string | "create-note" | "search-notes" | "external-link";
parentNoteId?: string;
icon?: string;
}
interface Options {
@ -262,7 +264,7 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
},
displayKey: "notePathTitle",
templates: {
suggestion: (suggestion) => suggestion.highlightedNotePathTitle
suggestion: (suggestion) => `<span class="${suggestion.icon ?? "bx bx-note"}"></span> ${suggestion.highlightedNotePathTitle}`
},
// we can't cache identical searches because notes can be created / renamed, new recent notes can be added
cache: false

View File

@ -19,15 +19,15 @@ const TPL = /*html*/`
margin-top: 10px;
}
.note-path-list .path-current {
.note-path-list .path-current a {
font-weight: bold;
}
.note-path-list .path-archived {
.note-path-list .path-archived a {
color: var(--muted-text-color) !important;
}
.note-path-list .path-search {
.note-path-list .path-search a {
font-style: italic;
}
</style>
@ -72,7 +72,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
this.$notePathList.empty();
if (!this.note || this.noteId === "root") {
this.$notePathList.empty().append(await this.getRenderedPath("root"));
this.$notePathList.empty().append(await this.getRenderedPath(["root"]));
return;
}
@ -88,7 +88,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
const renderedPaths = [];
for (const notePathRecord of sortedNotePaths) {
const notePath = notePathRecord.notePath.join("/");
const notePath = notePathRecord.notePath;
renderedPaths.push(await this.getRenderedPath(notePath, notePathRecord));
}
@ -96,42 +96,54 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
this.$notePathList.empty().append(...renderedPaths);
}
async getRenderedPath(notePath: string, notePathRecord: NotePathRecord | null = null) {
const title = await treeService.getNotePathTitle(notePath);
async getRenderedPath(notePath: string[], notePathRecord: NotePathRecord | null = null) {
const $pathItem = $("<li>");
const pathSegments: string[] = [];
const lastIndex = notePath.length - 1;
const $noteLink = await linkService.createLink(notePath, { title });
for (let i = 0; i < notePath.length; i++) {
const noteId = notePath[i];
pathSegments.push(noteId);
const title = await treeService.getNoteTitle(noteId);
const $noteLink = await linkService.createLink(pathSegments.join("/"), { title });
$noteLink.find("a").addClass("no-tooltip-preview tn-link");
$noteLink.find("a").addClass("no-tooltip-preview tn-link");
$pathItem.append($noteLink);
if (i != lastIndex) {
$pathItem.append(" / ");
}
}
const icons = [];
if (this.notePath === notePath) {
$noteLink.addClass("path-current");
if (this.notePath === notePath.join("/")) {
$pathItem.addClass("path-current");
}
if (!notePathRecord || notePathRecord.isInHoistedSubTree) {
$noteLink.addClass("path-in-hoisted-subtree");
$pathItem.addClass("path-in-hoisted-subtree");
} else {
icons.push(`<span class="bx bx-trending-up" title="${t("note_paths.outside_hoisted")}"></span>`);
}
if (notePathRecord?.isArchived) {
$noteLink.addClass("path-archived");
$pathItem.addClass("path-archived");
icons.push(`<span class="bx bx-archive" title="${t("note_paths.archived")}"></span>`);
}
if (notePathRecord?.isSearch) {
$noteLink.addClass("path-search");
$pathItem.addClass("path-search");
icons.push(`<span class="bx bx-search" title="${t("note_paths.search")}"></span>`);
}
if (icons.length > 0) {
$noteLink.append(` ${icons.join(" ")}`);
$pathItem.append(` ${icons.join(" ")}`);
}
return $("<li>").append($noteLink);
return $pathItem;
}
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {

View File

@ -75,7 +75,8 @@ function getRecentNotes(activeNoteId: string) {
notePath: rn.notePath,
noteTitle: title,
notePathTitle,
highlightedNotePathTitle: `<span class="${icon ?? "bx bx-note"}"></span> ${notePathTitle}`
highlightedNotePathTitle: utils.escapeHtml(notePathTitle),
icon: icon ?? "bx bx-note"
};
});
}

View File

@ -13,11 +13,12 @@ function getFullAnonymizationScript() {
.map((attr) => `'${attr.name}'`)
.join(", ");
const anonymizeScript = `
const anonymizeScript = /*sql*/`\
UPDATE etapi_tokens SET tokenHash = 'API token hash value';
UPDATE notes SET title = 'title' WHERE title NOT IN ('root', '_hidden', '_share');
UPDATE blobs SET content = 'text' WHERE content IS NOT NULL;
UPDATE revisions SET title = 'title';
UPDATE attachments SET title = 'title';
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN(${builtinAttrNames});
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN (${builtinAttrNames});

View File

@ -29,6 +29,9 @@ export interface TriliumConfig {
certPath: string;
keyPath: string;
trustedReverseProxy: boolean | string;
corsAllowOrigin: string;
corsAllowMethods: string;
corsAllowHeaders: string;
};
Session: {
cookieMaxAge: number;
@ -79,7 +82,16 @@ const config: TriliumConfig = {
process.env.TRILIUM_NETWORK_KEYPATH || iniConfig.Network.keyPath || "",
trustedReverseProxy:
process.env.TRILIUM_NETWORK_TRUSTEDREVERSEPROXY || iniConfig.Network.trustedReverseProxy || false
process.env.TRILIUM_NETWORK_TRUSTEDREVERSEPROXY || iniConfig.Network.trustedReverseProxy || false,
corsAllowOrigin:
process.env.TRILIUM_NETWORK_CORS_ALLOW_ORIGIN || iniConfig.Network.corsAllowOrigin || "",
corsAllowMethods:
process.env.TRILIUM_NETWORK_CORS_ALLOW_METHODS || iniConfig.Network.corsAllowMethods || "",
corsAllowHeaders:
process.env.TRILIUM_NETWORK_CORS_ALLOW_HEADERS || iniConfig.Network.corsAllowHeaders || ""
},
Session: {

View File

@ -321,4 +321,25 @@ describe("Markdown export", () => {
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("exports todo lists properly", () => {
const html = trimIndentation/*html*/`\
<ul class="todo-list">
<li>
<label class="todo-list__label">
<input type="checkbox" checked="checked" disabled="disabled"><span class="todo-list__label__description">Hello</span>
</label>
</li>
<li>
<label class="todo-list__label">
<input type="checkbox" disabled="disabled"><span class="todo-list__label__description">World</span>
</label>
</li>
</ul>
`;
const expected = trimIndentation`\
- [x] Hello
- [ ] World`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
});

View File

@ -230,7 +230,11 @@ function buildListItemFilter(): Rule {
var start = parent.getAttribute('start')
var index = Array.prototype.indexOf.call(parent.children, node)
prefix = (start ? Number(start) + index : index + 1) + '. '
} else if (parent.classList.contains("todo-list")) {
const isChecked = node.querySelector("input[type=checkbox]:checked");
prefix = (isChecked ? "- [x] " : "- [ ] ");
}
const result = prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : '');
return result;
}

View File

@ -233,4 +233,12 @@ second line 2</code></pre><ul><li>Hello</li><li>world</li></ul><ol><li>Hello</li
expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
});
it("imports todo lists properly", () => {
const input = trimIndentation`\
- [x] Hello
- [ ] World`;
const expected = `<ul class="todo-list"><li><label class="todo-list__label"><input type="checkbox" checked="checked" disabled="disabled"><span class="todo-list__label__description">Hello</span></label></li><li><label class="todo-list__label"><input type="checkbox" disabled="disabled"><span class="todo-list__label__description">World</span></label></li></ul>`;
expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
});
});

View File

@ -48,12 +48,52 @@ class CustomMarkdownRenderer extends Renderer {
}
list(token: Tokens.List): string {
return super.list(token)
let result = super.list(token)
.replace("\n", "") // we replace the first one only.
.trimEnd();
// Handle todo-list in the CKEditor format.
if (token.items.some(item => item.task)) {
result = result.replace(/^<ul>/, "<ul class=\"todo-list\">");
}
return result;
}
checkbox({ checked }: Tokens.Checkbox): string {
return '<input type="checkbox"'
+ (checked ? 'checked="checked" ' : '')
+ 'disabled="disabled">';
}
listitem(item: Tokens.ListItem): string {
// Handle todo-list in the CKEditor format.
if (item.task) {
let itemBody = '';
const checkbox = this.checkbox({ checked: !!item.checked });
if (item.loose) {
if (item.tokens[0]?.type === 'paragraph') {
item.tokens[0].text = checkbox + item.tokens[0].text;
if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') {
item.tokens[0].tokens[0].text = checkbox + escape(item.tokens[0].tokens[0].text);
item.tokens[0].tokens[0].escaped = true;
}
} else {
item.tokens.unshift({
type: 'text',
raw: checkbox,
text: checkbox,
escaped: true,
});
}
} else {
itemBody += checkbox;
}
itemBody += `<span class="todo-list__label__description">${this.parser.parse(item.tokens, !!item.loose)}</span>`;
return `<li><label class="todo-list__label">${itemBody}</label></li>`;
}
return super.listitem(item).trimEnd();
}

View File

@ -358,8 +358,9 @@ function searchNotesForAutocomplete(query: string, fastSearch: boolean = true) {
return {
notePath: result.notePath,
noteTitle: title,
notePathTitle: `${icon} ${result.notePathTitle}`,
highlightedNotePathTitle: `<span class="${icon ?? "bx bx-note"}"></span> ${result.highlightedNotePathTitle}`
notePathTitle: result.notePathTitle,
highlightedNotePathTitle: result.highlightedNotePathTitle,
icon: icon ?? "bx bx-note"
};
});
}