From a7799d32b0332e9c6d9edde186522cb67d33f3f8 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Tue, 1 Apr 2025 18:47:07 +0800 Subject: [PATCH 1/3] To prevent search lag when there are a large number of notes --- src/public/app/services/note_autocomplete.ts | 49 ++++++++++++++++---- src/routes/api/stats.ts | 11 ++++- src/routes/routes.ts | 1 + 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/public/app/services/note_autocomplete.ts b/src/public/app/services/note_autocomplete.ts index ca9894b1f..e879c87f1 100644 --- a/src/public/app/services/note_autocomplete.ts +++ b/src/public/app/services/note_autocomplete.ts @@ -10,6 +10,18 @@ const SELECTED_NOTE_PATH_KEY = "data-note-path"; const SELECTED_EXTERNAL_LINK_KEY = "data-external-link"; +// To prevent search lag when there are a large number of notes, set a delay based on the number of notes to avoid jitter. +const notesCount = await server.get(`stats/notesCount`); +let debounceTimeoutId: ReturnType; + +function getSearchDelay(notesCount: number): number { + const maxNotes = 20000; + const maxDelay = 1000; + const delay = Math.min(maxDelay, (notesCount / maxNotes) * maxDelay); + return delay; +} +let searchDelay = getSearchDelay(notesCount); + export interface Suggestion { noteTitle?: string; externalLink?: string; @@ -72,10 +84,9 @@ async function autocompleteSource(term: string, cb: (rows: Suggestion[]) => void const activeNoteId = appContext.tabManager.getActiveContextNoteId(); const length = term.trim().length; - let results: Suggestion[] = []; - if (length >= 3) { - results = await server.get(`autocomplete?query=${encodeURIComponent(term)}&activeNoteId=${activeNoteId}&fastSearch=${fastSearch}`); - } + let results = await server.get(`autocomplete?query=${encodeURIComponent(term)}&activeNoteId=${activeNoteId}&fastSearch=${fastSearch}`); + + options.fastSearch = true; if (length >= 1 && options.allowCreatingNotes) { results = [ @@ -112,6 +123,7 @@ async function autocompleteSource(term: string, cb: (rows: Suggestion[]) => void } function clearText($el: JQuery) { + searchDelay = 0; $el.setSelectedNotePath(""); $el.autocomplete("val", "").trigger("change"); } @@ -122,6 +134,7 @@ function setText($el: JQuery, text: string) { } function showRecentNotes($el: JQuery) { + searchDelay = 0; $el.setSelectedNotePath(""); $el.autocomplete("val", ""); $el.autocomplete("open"); @@ -137,11 +150,8 @@ function fullTextSearch($el: JQuery, options: Options) { options.fastSearch = false; $el.autocomplete("val", ""); $el.setSelectedNotePath(""); + searchDelay = 0; $el.autocomplete("val", searchString); - // Set a delay to avoid resetting to true before full text search (await server.get) is called. - setTimeout(() => { - options.fastSearch = true; - }, 100); } function initNoteAutocomplete($el: JQuery, options?: Options) { @@ -154,6 +164,15 @@ function initNoteAutocomplete($el: JQuery, options?: Options) { options = options || {}; + // Used to track whether the user is performing character composition with an input method (such as Chinese Pinyin, Japanese, Korean, etc.) and to avoid triggering a search during the composition process. + let isComposingInput = false; + $el.on("compositionstart", () => { + isComposingInput = true; + }); + $el.on("compositionend", () => { + isComposingInput = false; + }); + $el.addClass("note-autocomplete-input"); const $clearTextButton = $("").addClass("input-group-text input-clearer-button bx bxs-tag-x").prop("title", t("note_autocomplete.clear-text-field")); @@ -226,7 +245,19 @@ function initNoteAutocomplete($el: JQuery, options?: Options) { }, [ { - source: (term, cb) => autocompleteSource(term, cb, options), + source: (term, cb) => { + clearTimeout(debounceTimeoutId); + debounceTimeoutId = setTimeout(() => { + if (isComposingInput) { + return; + } + autocompleteSource(term, cb, options); + }, searchDelay); + + if (searchDelay === 0) { + searchDelay = getSearchDelay(notesCount); + } + }, displayKey: "notePathTitle", templates: { suggestion: (suggestion) => suggestion.highlightedNotePathTitle diff --git a/src/routes/api/stats.ts b/src/routes/api/stats.ts index 15e28f083..8f953bc0b 100644 --- a/src/routes/api/stats.ts +++ b/src/routes/api/stats.ts @@ -48,7 +48,16 @@ function getSubtreeSize(req: Request) { }; } +// Get the total number of notes +function getNotesCount(req: Request) { + const notesCount = sql.getRow( + `SELECT COUNT(*) AS count FROM notes WHERE isDeleted = 0;`, + ) as { count: number }; + return notesCount.count; +} + export default { getNoteSize, - getSubtreeSize + getSubtreeSize, + getNotesCount }; diff --git a/src/routes/routes.ts b/src/routes/routes.ts index abae1acaa..4b09def4f 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -361,6 +361,7 @@ function register(app: express.Application) { apiRoute(GET, "/api/similar-notes/:noteId", similarNotesRoute.getSimilarNotes); apiRoute(GET, "/api/backend-log", backendLogRoute.getBackendLog); apiRoute(GET, "/api/stats/note-size/:noteId", statsRoute.getNoteSize); + apiRoute(GET, "/api/stats/notesCount", statsRoute.getNotesCount); apiRoute(GET, "/api/stats/subtree-size/:noteId", statsRoute.getSubtreeSize); apiRoute(PST, "/api/delete-notes-preview", notesApiRoute.getDeleteNotesPreview); route(GET, "/api/fonts", [auth.checkApiAuthOrElectron], fontsRoute.getFontCss); From adcb803caafe94aec87495e011436ddfb421366d Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Tue, 1 Apr 2025 21:07:15 +0800 Subject: [PATCH 2/3] To prevent search lag when there are a large number of notes --- src/public/app/services/note_autocomplete.ts | 3 ++- src/routes/api/autocomplete.ts | 12 +++++++++++- src/routes/api/stats.ts | 11 +---------- src/routes/routes.ts | 2 +- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/public/app/services/note_autocomplete.ts b/src/public/app/services/note_autocomplete.ts index e879c87f1..c194e257e 100644 --- a/src/public/app/services/note_autocomplete.ts +++ b/src/public/app/services/note_autocomplete.ts @@ -11,7 +11,8 @@ const SELECTED_NOTE_PATH_KEY = "data-note-path"; const SELECTED_EXTERNAL_LINK_KEY = "data-external-link"; // To prevent search lag when there are a large number of notes, set a delay based on the number of notes to avoid jitter. -const notesCount = await server.get(`stats/notesCount`); +const notesCount = await server.get(`autocomplete/notesCount`); +console.log(notesCount); let debounceTimeoutId: ReturnType; function getSearchDelay(notesCount: number): number { diff --git a/src/routes/api/autocomplete.ts b/src/routes/api/autocomplete.ts index a357ca4b3..584c2c88a 100644 --- a/src/routes/api/autocomplete.ts +++ b/src/routes/api/autocomplete.ts @@ -8,6 +8,7 @@ import cls from "../../services/cls.js"; import becca from "../../becca/becca.js"; import type { Request } from "express"; import ValidationError from "../../errors/validation_error.js"; +import sql from "../../services/sql.js"; function getAutocomplete(req: Request) { if (typeof req.query.query !== "string") { @@ -79,6 +80,15 @@ function getRecentNotes(activeNoteId: string) { }); } +// Get the total number of notes +function getNotesCount(req: Request) { + const notesCount = sql.getRow( + `SELECT COUNT(*) AS count FROM notes WHERE isDeleted = 0;`, + ) as { count: number }; + return notesCount.count; +} + export default { - getAutocomplete + getAutocomplete, + getNotesCount }; diff --git a/src/routes/api/stats.ts b/src/routes/api/stats.ts index 8f953bc0b..15e28f083 100644 --- a/src/routes/api/stats.ts +++ b/src/routes/api/stats.ts @@ -48,16 +48,7 @@ function getSubtreeSize(req: Request) { }; } -// Get the total number of notes -function getNotesCount(req: Request) { - const notesCount = sql.getRow( - `SELECT COUNT(*) AS count FROM notes WHERE isDeleted = 0;`, - ) as { count: number }; - return notesCount.count; -} - export default { getNoteSize, - getSubtreeSize, - getNotesCount + getSubtreeSize }; diff --git a/src/routes/routes.ts b/src/routes/routes.ts index 4b09def4f..a08a37c9b 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -273,6 +273,7 @@ function register(app: express.Application) { route(PST, "/api/setup/sync-seed", [auth.checkAppNotInitialized], setupApiRoute.saveSyncSeed, apiResultHandler, false); apiRoute(GET, "/api/autocomplete", autocompleteApiRoute.getAutocomplete); + apiRoute(GET, "/api/autocomplete/notesCount", autocompleteApiRoute.getNotesCount); apiRoute(GET, "/api/quick-search/:searchString", searchRoute.quickSearch); apiRoute(GET, "/api/search-note/:noteId", searchRoute.searchFromNote); apiRoute(PST, "/api/search-and-execute-note/:noteId", searchRoute.searchAndExecute); @@ -361,7 +362,6 @@ function register(app: express.Application) { apiRoute(GET, "/api/similar-notes/:noteId", similarNotesRoute.getSimilarNotes); apiRoute(GET, "/api/backend-log", backendLogRoute.getBackendLog); apiRoute(GET, "/api/stats/note-size/:noteId", statsRoute.getNoteSize); - apiRoute(GET, "/api/stats/notesCount", statsRoute.getNotesCount); apiRoute(GET, "/api/stats/subtree-size/:noteId", statsRoute.getSubtreeSize); apiRoute(PST, "/api/delete-notes-preview", notesApiRoute.getDeleteNotesPreview); route(GET, "/api/fonts", [auth.checkApiAuthOrElectron], fontsRoute.getFontCss); From b94bda66708ceb7bbfd4b2627d91f1f003c0011e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 1 Apr 2025 22:07:59 +0300 Subject: [PATCH 3/3] chore(note_autocomplete): remove logging --- src/public/app/services/note_autocomplete.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/public/app/services/note_autocomplete.ts b/src/public/app/services/note_autocomplete.ts index c194e257e..ae2522e18 100644 --- a/src/public/app/services/note_autocomplete.ts +++ b/src/public/app/services/note_autocomplete.ts @@ -1,6 +1,5 @@ import server from "./server.js"; import appContext from "../components/app_context.js"; -import utils from "./utils.js"; import noteCreateService from "./note_create.js"; import froca from "./froca.js"; import { t } from "./i18n.js"; @@ -12,12 +11,11 @@ const SELECTED_EXTERNAL_LINK_KEY = "data-external-link"; // To prevent search lag when there are a large number of notes, set a delay based on the number of notes to avoid jitter. const notesCount = await server.get(`autocomplete/notesCount`); -console.log(notesCount); let debounceTimeoutId: ReturnType; function getSearchDelay(notesCount: number): number { - const maxNotes = 20000; - const maxDelay = 1000; + const maxNotes = 20000; + const maxDelay = 1000; const delay = Math.min(maxDelay, (notesCount / maxNotes) * maxDelay); return delay; } @@ -168,10 +166,10 @@ function initNoteAutocomplete($el: JQuery, options?: Options) { // Used to track whether the user is performing character composition with an input method (such as Chinese Pinyin, Japanese, Korean, etc.) and to avoid triggering a search during the composition process. let isComposingInput = false; $el.on("compositionstart", () => { - isComposingInput = true; + isComposingInput = true; }); $el.on("compositionend", () => { - isComposingInput = false; + isComposingInput = false; }); $el.addClass("note-autocomplete-input"); @@ -253,8 +251,8 @@ function initNoteAutocomplete($el: JQuery, options?: Options) { return; } autocompleteSource(term, cb, options); - }, searchDelay); - + }, searchDelay); + if (searchDelay === 0) { searchDelay = getSearchDelay(notesCount); }