mirror of
https://github.com/zadam/trilium.git
synced 2026-05-06 10:46:57 +02:00
chore(standalone): fix typecheck
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
import { note_service } from "@triliumnext/core";
|
||||
export { findBookmarks } from "@triliumnext/core";
|
||||
export default note_service;
|
||||
|
||||
@@ -12,15 +12,12 @@
|
||||
* - These tests focus on the in-memory/CPU-bound parts of the pipeline
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import searchService from "./search.js";
|
||||
import BNote from "../../../becca/entities/bnote.js";
|
||||
import BBranch from "../../../becca/entities/bbranch.js";
|
||||
import SearchContext from "../search_context.js";
|
||||
import becca from "../../../becca/becca.js";
|
||||
import beccaService from "../../../becca/becca_service.js";
|
||||
import { NoteBuilder, note, id } from "../../../test/becca_mocking.js";
|
||||
import SearchResult from "../search_result.js";
|
||||
import { normalizeSearchText } from "../utils/text_utils.js";
|
||||
import { becca, becca_service as beccaService, BNote, BBranch, search as searchService, SearchContext, becca_mocking } from "@triliumnext/core";
|
||||
import SearchResult from "@triliumnext/core/src/services/search/search_result.js";
|
||||
import { normalizeSearchText } from "@triliumnext/core/src/services/search/utils/text_utils.js";
|
||||
|
||||
const { NoteBuilder, note } = becca_mocking;
|
||||
type NoteBuilder = InstanceType<typeof NoteBuilder>;
|
||||
|
||||
// ── helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
import { becca_mocking } from "@triliumnext/core";
|
||||
export const NoteBuilder = becca_mocking.NoteBuilder;
|
||||
export { NoteBuilder, note, id, findNoteByTitle } from "@triliumnext/core/src/test/becca_mocking.js";
|
||||
|
||||
@@ -5,7 +5,7 @@ import AbstractBeccaEntity from "./abstract_becca_entity.js";
|
||||
import dateUtils from "../../services/utils/date";
|
||||
import promotedAttributeDefinitionParser from "../../services/promoted_attribute_definition_parser.js";
|
||||
import type { AttributeRow, AttributeType } from "@triliumnext/commons";
|
||||
import { sanitizeAttributeName } from "../../services/utils/index.js";
|
||||
import { normalize, sanitizeAttributeName } from "../../services/utils/index.js";
|
||||
|
||||
interface SavingOpts {
|
||||
skipValidation?: boolean;
|
||||
@@ -34,6 +34,11 @@ class BAttribute extends AbstractBeccaEntity<BAttribute> {
|
||||
value!: string;
|
||||
isInheritable!: boolean;
|
||||
|
||||
/** Pre-normalized (lowercase, diacritics removed) name for search. */
|
||||
normalizedName!: string;
|
||||
/** Pre-normalized (lowercase, diacritics removed) value for search. */
|
||||
normalizedValue!: string;
|
||||
|
||||
constructor(row?: AttributeRow) {
|
||||
super();
|
||||
|
||||
@@ -59,6 +64,10 @@ class BAttribute extends AbstractBeccaEntity<BAttribute> {
|
||||
this.isInheritable = !!isInheritable;
|
||||
this.utcDateModified = utcDateModified;
|
||||
|
||||
// Pre-compute normalized forms for search (avoids repeated normalize() calls in hot loops)
|
||||
this.normalizedName = normalize(this.name);
|
||||
this.normalizedValue = normalize(this.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -194,6 +203,11 @@ class BAttribute extends AbstractBeccaEntity<BAttribute> {
|
||||
|
||||
super.beforeSaving();
|
||||
|
||||
// Recompute normalized fields in case name/value were modified directly
|
||||
// (e.g., attr.value = "..." followed by attr.save())
|
||||
this.normalizedName = normalize(this.name);
|
||||
this.normalizedValue = normalize(this.value);
|
||||
|
||||
this.becca.attributes[this.attributeId] = this;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ export { default as SearchContext } from "./services/search/search_context";
|
||||
export { default as search, } from "./services/search/services/search";
|
||||
export { type default as SearchResult } from "./services/search/search_result";
|
||||
export { type SearchParams } from "./services/search/services/types";
|
||||
export { default as note_service } from "./services/notes";
|
||||
export { default as note_service, findBookmarks } from "./services/notes";
|
||||
export type { NoteParams } from "./services/notes";
|
||||
export * as sanitize from "./services/sanitizer";
|
||||
export * as routes from "./routes";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { beforeAll, describe, expect, it } from "vitest";
|
||||
|
||||
import becca from "../../becca/becca.js";
|
||||
import type BNote from "../../becca/entities/bnote.js";
|
||||
import cls from "../cls.js";
|
||||
import { getContext } from "../context.js";
|
||||
import sql_init from "../sql_init.js";
|
||||
import TaskContext from "../task_context.js";
|
||||
import enex from "./enex.js";
|
||||
@@ -17,7 +17,7 @@ async function testImport(fileName: string) {
|
||||
const taskContext = TaskContext.getInstance("import-enex", "importNotes", {});
|
||||
|
||||
return new Promise<{ importedNote: BNote; rootNote: BNote }>((resolve, reject) => {
|
||||
cls.init(async () => {
|
||||
getContext().init(async () => {
|
||||
const rootNote = becca.getNote("root");
|
||||
if (!rootNote) {
|
||||
expect(rootNote).toBeTruthy();
|
||||
|
||||
@@ -512,6 +512,54 @@ function findIncludeNoteLinks(content: string, foundLinks: FoundLink[]) {
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts bookmark IDs from CKEditor bookmark anchors (`<a id="..."></a>` without href).
|
||||
* Bookmarks are stored as labels on the note so they can be looked up without parsing content.
|
||||
*/
|
||||
export function findBookmarks(content: string): string[] {
|
||||
const re = /<a\s+id="([^"]+)"[^>]*>(<\/a>)?/g;
|
||||
const bookmarks: string[] = [];
|
||||
let match;
|
||||
|
||||
while ((match = re.exec(content))) {
|
||||
// Skip anchors that also have an href (those are regular links, not bookmarks)
|
||||
if (match[0].includes("href=")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const id = match[1];
|
||||
if (!bookmarks.includes(id)) {
|
||||
bookmarks.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
return bookmarks;
|
||||
}
|
||||
|
||||
function saveBookmarks(note: BNote, content: string) {
|
||||
const foundBookmarks = findBookmarks(content);
|
||||
const existingBookmarks = note.getOwnedLabels("internalBookmark");
|
||||
|
||||
for (const bookmarkId of foundBookmarks) {
|
||||
const existing = existingBookmarks.find((l) => l.value === bookmarkId);
|
||||
|
||||
if (!existing) {
|
||||
new BAttribute({
|
||||
noteId: note.noteId,
|
||||
type: "label",
|
||||
name: "internalBookmark",
|
||||
value: bookmarkId
|
||||
}).save();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove bookmarks that are no longer in the content
|
||||
const unusedBookmarks = existingBookmarks.filter((l) => !foundBookmarks.includes(l.value));
|
||||
for (const unused of unusedBookmarks) {
|
||||
unused.markAsDeleted();
|
||||
}
|
||||
}
|
||||
|
||||
function findRelationMapLinks(content: string, foundLinks: FoundLink[]) {
|
||||
try {
|
||||
const obj = JSON.parse(content);
|
||||
@@ -727,6 +775,7 @@ function saveLinks(note: BNote, content: string | Uint8Array) {
|
||||
content = findImageLinks(content, foundLinks);
|
||||
content = findInternalLinks(content, foundLinks);
|
||||
content = findIncludeNoteLinks(content, foundLinks);
|
||||
saveBookmarks(note, content);
|
||||
|
||||
({ forceFrontendReload, content } = checkImageAttachments(note, content));
|
||||
} else if (note.type === "relationMap" && typeof content === "string") {
|
||||
|
||||
@@ -54,22 +54,22 @@ describe("sanitize", () => {
|
||||
describe("bookmark anchors", () => {
|
||||
it("preserves id attribute on empty <a> tags (CKEditor bookmarks)", () => {
|
||||
const dirty = `<a id="my-bookmark"></a>`;
|
||||
expect(html_sanitizer.sanitize(dirty)).toBe(dirty);
|
||||
expect(sanitizeHtml(dirty)).toBe(dirty);
|
||||
});
|
||||
|
||||
it("preserves id attribute on <a> tags with bookmark class", () => {
|
||||
const dirty = `<a id="chapter-1" class="ck-bookmark"></a>`;
|
||||
expect(html_sanitizer.sanitize(dirty)).toBe(dirty);
|
||||
expect(sanitizeHtml(dirty)).toBe(dirty);
|
||||
});
|
||||
|
||||
it("strips id attribute from non-anchor tags to prevent DOM clobbering", () => {
|
||||
const dirty = `<div id="loginForm">content</div>`;
|
||||
expect(html_sanitizer.sanitize(dirty)).toBe(`<div>content</div>`);
|
||||
expect(sanitizeHtml(dirty)).toBe(`<div>content</div>`);
|
||||
});
|
||||
|
||||
it("strips id attribute from <img> tags to prevent DOM clobbering", () => {
|
||||
const dirty = `<img id="someId" src="test.png" />`;
|
||||
expect(html_sanitizer.sanitize(dirty)).toBe(`<img src="test.png" />`);
|
||||
expect(sanitizeHtml(dirty)).toBe(`<img src="test.png" />`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
import hoistedNoteService from "../hoisted_note.js";
|
||||
import optionService from "../options.js";
|
||||
import type { SearchParams } from "./services/types.js";
|
||||
|
||||
class SearchContext {
|
||||
@@ -19,6 +20,8 @@ class SearchContext {
|
||||
debugInfo: {} | null;
|
||||
fuzzyAttributeSearch: boolean;
|
||||
enableFuzzyMatching: boolean; // Controls whether fuzzy matching is enabled for this search phase
|
||||
/** When true, skip the two-phase fuzzy fallback and use the single-token fast path. */
|
||||
autocomplete: boolean;
|
||||
highlightedTokens: string[];
|
||||
originalQuery: string;
|
||||
fulltextQuery: string;
|
||||
@@ -46,7 +49,12 @@ class SearchContext {
|
||||
this.debug = params.debug;
|
||||
this.debugInfo = null;
|
||||
this.fuzzyAttributeSearch = !!params.fuzzyAttributeSearch;
|
||||
this.enableFuzzyMatching = true; // Default to true for backward compatibility
|
||||
this.autocomplete = !!params.autocomplete;
|
||||
try {
|
||||
this.enableFuzzyMatching = optionService.getOptionBool("searchEnableFuzzyMatching");
|
||||
} catch {
|
||||
this.enableFuzzyMatching = true; // Default to true if option not yet initialized
|
||||
}
|
||||
this.highlightedTokens = [];
|
||||
this.originalQuery = "";
|
||||
this.fulltextQuery = ""; // complete fulltext part
|
||||
|
||||
Reference in New Issue
Block a user