mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-28 10:32:27 +08:00
Merge pull request #1589 from TriliumNext/autoComplete
To prevent search lag when there are a large number of notes
This commit is contained in:
commit
d7fef36b46
@ -1,6 +1,5 @@
|
|||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
import appContext from "../components/app_context.js";
|
import appContext from "../components/app_context.js";
|
||||||
import utils from "./utils.js";
|
|
||||||
import noteCreateService from "./note_create.js";
|
import noteCreateService from "./note_create.js";
|
||||||
import froca from "./froca.js";
|
import froca from "./froca.js";
|
||||||
import { t } from "./i18n.js";
|
import { t } from "./i18n.js";
|
||||||
@ -10,6 +9,18 @@ const SELECTED_NOTE_PATH_KEY = "data-note-path";
|
|||||||
|
|
||||||
const SELECTED_EXTERNAL_LINK_KEY = "data-external-link";
|
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<number>(`autocomplete/notesCount`);
|
||||||
|
let debounceTimeoutId: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
|
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 {
|
export interface Suggestion {
|
||||||
noteTitle?: string;
|
noteTitle?: string;
|
||||||
externalLink?: string;
|
externalLink?: string;
|
||||||
@ -72,10 +83,9 @@ async function autocompleteSource(term: string, cb: (rows: Suggestion[]) => void
|
|||||||
const activeNoteId = appContext.tabManager.getActiveContextNoteId();
|
const activeNoteId = appContext.tabManager.getActiveContextNoteId();
|
||||||
const length = term.trim().length;
|
const length = term.trim().length;
|
||||||
|
|
||||||
let results: Suggestion[] = [];
|
let results = await server.get<Suggestion[]>(`autocomplete?query=${encodeURIComponent(term)}&activeNoteId=${activeNoteId}&fastSearch=${fastSearch}`);
|
||||||
if (length >= 3) {
|
|
||||||
results = await server.get<Suggestion[]>(`autocomplete?query=${encodeURIComponent(term)}&activeNoteId=${activeNoteId}&fastSearch=${fastSearch}`);
|
options.fastSearch = true;
|
||||||
}
|
|
||||||
|
|
||||||
if (length >= 1 && options.allowCreatingNotes) {
|
if (length >= 1 && options.allowCreatingNotes) {
|
||||||
results = [
|
results = [
|
||||||
@ -112,6 +122,7 @@ async function autocompleteSource(term: string, cb: (rows: Suggestion[]) => void
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clearText($el: JQuery<HTMLElement>) {
|
function clearText($el: JQuery<HTMLElement>) {
|
||||||
|
searchDelay = 0;
|
||||||
$el.setSelectedNotePath("");
|
$el.setSelectedNotePath("");
|
||||||
$el.autocomplete("val", "").trigger("change");
|
$el.autocomplete("val", "").trigger("change");
|
||||||
}
|
}
|
||||||
@ -122,6 +133,7 @@ function setText($el: JQuery<HTMLElement>, text: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showRecentNotes($el: JQuery<HTMLElement>) {
|
function showRecentNotes($el: JQuery<HTMLElement>) {
|
||||||
|
searchDelay = 0;
|
||||||
$el.setSelectedNotePath("");
|
$el.setSelectedNotePath("");
|
||||||
$el.autocomplete("val", "");
|
$el.autocomplete("val", "");
|
||||||
$el.autocomplete("open");
|
$el.autocomplete("open");
|
||||||
@ -137,11 +149,8 @@ function fullTextSearch($el: JQuery<HTMLElement>, options: Options) {
|
|||||||
options.fastSearch = false;
|
options.fastSearch = false;
|
||||||
$el.autocomplete("val", "");
|
$el.autocomplete("val", "");
|
||||||
$el.setSelectedNotePath("");
|
$el.setSelectedNotePath("");
|
||||||
|
searchDelay = 0;
|
||||||
$el.autocomplete("val", searchString);
|
$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<HTMLElement>, options?: Options) {
|
function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||||
@ -154,6 +163,15 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
|||||||
|
|
||||||
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");
|
$el.addClass("note-autocomplete-input");
|
||||||
|
|
||||||
const $clearTextButton = $("<a>").addClass("input-group-text input-clearer-button bx bxs-tag-x").prop("title", t("note_autocomplete.clear-text-field"));
|
const $clearTextButton = $("<a>").addClass("input-group-text input-clearer-button bx bxs-tag-x").prop("title", t("note_autocomplete.clear-text-field"));
|
||||||
@ -226,7 +244,19 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, 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",
|
displayKey: "notePathTitle",
|
||||||
templates: {
|
templates: {
|
||||||
suggestion: (suggestion) => suggestion.highlightedNotePathTitle
|
suggestion: (suggestion) => suggestion.highlightedNotePathTitle
|
||||||
|
@ -8,6 +8,7 @@ import cls from "../../services/cls.js";
|
|||||||
import becca from "../../becca/becca.js";
|
import becca from "../../becca/becca.js";
|
||||||
import type { Request } from "express";
|
import type { Request } from "express";
|
||||||
import ValidationError from "../../errors/validation_error.js";
|
import ValidationError from "../../errors/validation_error.js";
|
||||||
|
import sql from "../../services/sql.js";
|
||||||
|
|
||||||
function getAutocomplete(req: Request) {
|
function getAutocomplete(req: Request) {
|
||||||
if (typeof req.query.query !== "string") {
|
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 {
|
export default {
|
||||||
getAutocomplete
|
getAutocomplete,
|
||||||
|
getNotesCount
|
||||||
};
|
};
|
||||||
|
@ -273,6 +273,7 @@ function register(app: express.Application) {
|
|||||||
route(PST, "/api/setup/sync-seed", [auth.checkAppNotInitialized], setupApiRoute.saveSyncSeed, apiResultHandler, false);
|
route(PST, "/api/setup/sync-seed", [auth.checkAppNotInitialized], setupApiRoute.saveSyncSeed, apiResultHandler, false);
|
||||||
|
|
||||||
apiRoute(GET, "/api/autocomplete", autocompleteApiRoute.getAutocomplete);
|
apiRoute(GET, "/api/autocomplete", autocompleteApiRoute.getAutocomplete);
|
||||||
|
apiRoute(GET, "/api/autocomplete/notesCount", autocompleteApiRoute.getNotesCount);
|
||||||
apiRoute(GET, "/api/quick-search/:searchString", searchRoute.quickSearch);
|
apiRoute(GET, "/api/quick-search/:searchString", searchRoute.quickSearch);
|
||||||
apiRoute(GET, "/api/search-note/:noteId", searchRoute.searchFromNote);
|
apiRoute(GET, "/api/search-note/:noteId", searchRoute.searchFromNote);
|
||||||
apiRoute(PST, "/api/search-and-execute-note/:noteId", searchRoute.searchAndExecute);
|
apiRoute(PST, "/api/search-and-execute-note/:noteId", searchRoute.searchAndExecute);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user