mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-11-04 15:11:31 +08:00 
			
		
		
		
	chore(client/ts): port services/note_autocomplete
This commit is contained in:
		
							parent
							
								
									934a395f15
								
							
						
					
					
						commit
						e889955e8b
					
				@ -40,6 +40,9 @@ export type TriggerData = {
 | 
				
			|||||||
    text: string;
 | 
					    text: string;
 | 
				
			||||||
} | {
 | 
					} | {
 | 
				
			||||||
    callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void
 | 
					    callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void
 | 
				
			||||||
 | 
					} | {
 | 
				
			||||||
 | 
					    // For "searchNotes"
 | 
				
			||||||
 | 
					    searchString: string | undefined;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AppContext extends Component {
 | 
					class AppContext extends Component {
 | 
				
			||||||
 | 
				
			|||||||
@ -243,7 +243,7 @@ class FrocaImpl implements Froca {
 | 
				
			|||||||
        }).filter(note => !!note) as FNote[];
 | 
					        }).filter(note => !!note) as FNote[];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getNotes(noteIds: string[], silentNotFoundError = false): Promise<FNote[]> {
 | 
					    async getNotes(noteIds: string[] | JQuery<string>, silentNotFoundError = false): Promise<FNote[]> {
 | 
				
			||||||
        noteIds = Array.from(new Set(noteIds)); // make unique
 | 
					        noteIds = Array.from(new Set(noteIds)); // make unique
 | 
				
			||||||
        const missingNoteIds = noteIds.filter(noteId => !this.notes[noteId]);
 | 
					        const missingNoteIds = noteIds.filter(noteId => !this.notes[noteId]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,26 @@ const SELECTED_NOTE_PATH_KEY = "data-note-path";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const SELECTED_EXTERNAL_LINK_KEY = "data-external-link";
 | 
					const SELECTED_EXTERNAL_LINK_KEY = "data-external-link";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function autocompleteSourceForCKEditor(queryText) {
 | 
					export interface Suggestion {
 | 
				
			||||||
 | 
					    noteTitle?: string;
 | 
				
			||||||
 | 
					    externalLink?: string;
 | 
				
			||||||
 | 
					    notePathTitle?: string;
 | 
				
			||||||
 | 
					    notePath?: string;
 | 
				
			||||||
 | 
					    highlightedNotePathTitle?: string;
 | 
				
			||||||
 | 
					    action?: string | "create-note" | "search-notes" | "external-link";
 | 
				
			||||||
 | 
					    parentNoteId?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Options {
 | 
				
			||||||
 | 
					    container?: HTMLElement;
 | 
				
			||||||
 | 
					    fastSearch?: boolean;
 | 
				
			||||||
 | 
					    allowCreatingNotes?: boolean;
 | 
				
			||||||
 | 
					    allowJumpToSearchNotes?: boolean;
 | 
				
			||||||
 | 
					    allowExternalLinks?: boolean;
 | 
				
			||||||
 | 
					    hideGoToSelectedNoteButton?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function autocompleteSourceForCKEditor(queryText: string) {
 | 
				
			||||||
    return await new Promise((res, rej) => {
 | 
					    return await new Promise((res, rej) => {
 | 
				
			||||||
        autocompleteSource(queryText, rows => {
 | 
					        autocompleteSource(queryText, rows => {
 | 
				
			||||||
            res(rows.map(row => {
 | 
					            res(rows.map(row => {
 | 
				
			||||||
@ -30,7 +49,7 @@ async function autocompleteSourceForCKEditor(queryText) {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function autocompleteSource(term, cb, options = {}) {
 | 
					async function autocompleteSource(term: string, cb: (rows: Suggestion[]) => void, options: Options = {}) {
 | 
				
			||||||
    const fastSearch = options.fastSearch === false ? false : true;
 | 
					    const fastSearch = options.fastSearch === false ? false : true;
 | 
				
			||||||
    if (fastSearch === false) {
 | 
					    if (fastSearch === false) {
 | 
				
			||||||
        if (term.trim().length === 0){
 | 
					        if (term.trim().length === 0){
 | 
				
			||||||
@ -46,7 +65,7 @@ async function autocompleteSource(term, cb, options = {}) {
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    const activeNoteId = appContext.tabManager.getActiveContextNoteId();
 | 
					    const activeNoteId = appContext.tabManager.getActiveContextNoteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let results = await server.get(`autocomplete?query=${encodeURIComponent(term)}&activeNoteId=${activeNoteId}&fastSearch=${fastSearch}`);
 | 
					    let results: Suggestion[] = await server.get<Suggestion[]>(`autocomplete?query=${encodeURIComponent(term)}&activeNoteId=${activeNoteId}&fastSearch=${fastSearch}`);
 | 
				
			||||||
    if (term.trim().length >= 1 && options.allowCreatingNotes) {
 | 
					    if (term.trim().length >= 1 && options.allowCreatingNotes) {
 | 
				
			||||||
        results = [
 | 
					        results = [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -54,7 +73,7 @@ async function autocompleteSource(term, cb, options = {}) {
 | 
				
			|||||||
                noteTitle: term,
 | 
					                noteTitle: term,
 | 
				
			||||||
                parentNoteId: activeNoteId || 'root',
 | 
					                parentNoteId: activeNoteId || 'root',
 | 
				
			||||||
                highlightedNotePathTitle: t("note_autocomplete.create-note", { term })
 | 
					                highlightedNotePathTitle: t("note_autocomplete.create-note", { term })
 | 
				
			||||||
            }
 | 
					            } as Suggestion
 | 
				
			||||||
        ].concat(results);
 | 
					        ].concat(results);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -74,14 +93,14 @@ async function autocompleteSource(term, cb, options = {}) {
 | 
				
			|||||||
                action: 'external-link',
 | 
					                action: 'external-link',
 | 
				
			||||||
                externalLink: term,
 | 
					                externalLink: term,
 | 
				
			||||||
                highlightedNotePathTitle: t("note_autocomplete.insert-external-link", { term })
 | 
					                highlightedNotePathTitle: t("note_autocomplete.insert-external-link", { term })
 | 
				
			||||||
            }
 | 
					            } as Suggestion
 | 
				
			||||||
        ].concat(results);
 | 
					        ].concat(results);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cb(results);
 | 
					    cb(results);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function clearText($el) {
 | 
					function clearText($el: JQuery<HTMLElement>) {
 | 
				
			||||||
    if (utils.isMobile()) {
 | 
					    if (utils.isMobile()) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -90,7 +109,7 @@ function clearText($el) {
 | 
				
			|||||||
    $el.autocomplete("val", "").trigger('change');
 | 
					    $el.autocomplete("val", "").trigger('change');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function setText($el, text) {
 | 
					function setText($el: JQuery<HTMLElement>, text: string) {
 | 
				
			||||||
    if (utils.isMobile()) {
 | 
					    if (utils.isMobile()) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -101,7 +120,7 @@ function setText($el, text) {
 | 
				
			|||||||
        .autocomplete("open");
 | 
					        .autocomplete("open");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function showRecentNotes($el) {
 | 
					function showRecentNotes($el:JQuery<HTMLElement>) {
 | 
				
			||||||
    if (utils.isMobile()) {
 | 
					    if (utils.isMobile()) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -112,21 +131,22 @@ function showRecentNotes($el) {
 | 
				
			|||||||
    $el.trigger('focus');
 | 
					    $el.trigger('focus');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function fullTextSearch($el, options){
 | 
					function fullTextSearch($el: JQuery<HTMLElement>, options: Options){
 | 
				
			||||||
    const searchString = $el.autocomplete('val');
 | 
					    const searchString = $el.autocomplete('val') as unknown as string;
 | 
				
			||||||
    if (options.fastSearch === false || searchString.trim().length === 0) {
 | 
					    if (options.fastSearch === false || searchString?.trim().length === 0) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }    
 | 
					    }    
 | 
				
			||||||
    $el.trigger('focus');
 | 
					    $el.trigger('focus');
 | 
				
			||||||
    options.fastSearch = false;
 | 
					    options.fastSearch = false;
 | 
				
			||||||
    $el.autocomplete('val', '');
 | 
					    $el.autocomplete('val', '');
 | 
				
			||||||
 | 
					    $el.autocomplete()
 | 
				
			||||||
    $el.setSelectedNotePath("");
 | 
					    $el.setSelectedNotePath("");
 | 
				
			||||||
    $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.
 | 
					    // Set a delay to avoid resetting to true before full text search (await server.get) is called.
 | 
				
			||||||
    setTimeout(() => { options.fastSearch = true; }, 100);
 | 
					    setTimeout(() => { options.fastSearch = true; }, 100);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function initNoteAutocomplete($el, options) {
 | 
					function initNoteAutocomplete($el: JQuery<HTMLElement>, options: Options) {
 | 
				
			||||||
    if ($el.hasClass("note-autocomplete-input") || utils.isMobile()) {
 | 
					    if ($el.hasClass("note-autocomplete-input") || utils.isMobile()) {
 | 
				
			||||||
        // clear any event listener added in previous invocation of this function
 | 
					        // clear any event listener added in previous invocation of this function
 | 
				
			||||||
        $el.off('autocomplete:noteselected');
 | 
					        $el.off('autocomplete:noteselected');
 | 
				
			||||||
@ -174,7 +194,7 @@ function initNoteAutocomplete($el, options) {
 | 
				
			|||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let autocompleteOptions = {};
 | 
					    let autocompleteOptions: AutoCompleteConfig = {};
 | 
				
			||||||
    if (options.container) {
 | 
					    if (options.container) {
 | 
				
			||||||
        autocompleteOptions.dropdownMenuContainer = options.container;
 | 
					        autocompleteOptions.dropdownMenuContainer = options.container;
 | 
				
			||||||
        autocompleteOptions.debug = true;   // don't close on blur
 | 
					        autocompleteOptions.debug = true;   // don't close on blur
 | 
				
			||||||
@ -221,7 +241,8 @@ function initNoteAutocomplete($el, options) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $el.on('autocomplete:selected', async (event, suggestion) => {
 | 
					    // TODO: Types fail due to "autocomplete:selected" not being registered in type definitions.
 | 
				
			||||||
 | 
					    ($el as any).on('autocomplete:selected', async (event: Event, suggestion: Suggestion) => {
 | 
				
			||||||
        if (suggestion.action === 'external-link') {
 | 
					        if (suggestion.action === 'external-link') {
 | 
				
			||||||
            $el.setSelectedNotePath(null);
 | 
					            $el.setSelectedNotePath(null);
 | 
				
			||||||
            $el.setSelectedExternalLink(suggestion.externalLink);
 | 
					            $el.setSelectedExternalLink(suggestion.externalLink);
 | 
				
			||||||
@ -250,7 +271,7 @@ function initNoteAutocomplete($el, options) {
 | 
				
			|||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId;
 | 
					            const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId;
 | 
				
			||||||
            suggestion.notePath = note.getBestNotePathString(hoistedNoteId);
 | 
					            suggestion.notePath = note?.getBestNotePathString(hoistedNoteId);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (suggestion.action === 'search-notes') {
 | 
					        if (suggestion.action === 'search-notes') {
 | 
				
			||||||
@ -270,7 +291,7 @@ function initNoteAutocomplete($el, options) {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $el.on('autocomplete:closed', () => {
 | 
					    $el.on('autocomplete:closed', () => {
 | 
				
			||||||
        if (!$el.val().trim()) {
 | 
					        if (!String($el.val())?.trim()) {
 | 
				
			||||||
            clearText($el);
 | 
					            clearText($el);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@ -289,7 +310,7 @@ function initNoteAutocomplete($el, options) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function init() {
 | 
					function init() {
 | 
				
			||||||
    $.fn.getSelectedNotePath = function () {
 | 
					    $.fn.getSelectedNotePath = function () {
 | 
				
			||||||
        if (!$(this).val().trim()) {
 | 
					        if (!String($(this).val())?.trim()) {
 | 
				
			||||||
            return "";
 | 
					            return "";
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            return $(this).attr(SELECTED_NOTE_PATH_KEY);
 | 
					            return $(this).attr(SELECTED_NOTE_PATH_KEY);
 | 
				
			||||||
@ -297,7 +318,8 @@ function init() {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $.fn.getSelectedNoteId = function () {
 | 
					    $.fn.getSelectedNoteId = function () {
 | 
				
			||||||
        const notePath = $(this).getSelectedNotePath();
 | 
					        const $el = $(this as unknown as HTMLElement);
 | 
				
			||||||
 | 
					        const notePath = $el.getSelectedNotePath();
 | 
				
			||||||
        if (!notePath) {
 | 
					        if (!notePath) {
 | 
				
			||||||
            return null;
 | 
					            return null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -320,7 +342,7 @@ function init() {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $.fn.getSelectedExternalLink = function () {
 | 
					    $.fn.getSelectedExternalLink = function () {
 | 
				
			||||||
        if (!$(this).val().trim()) {
 | 
					        if (!String($(this).val())?.trim()) {
 | 
				
			||||||
            return "";
 | 
					            return "";
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            return $(this).attr(SELECTED_EXTERNAL_LINK_KEY);
 | 
					            return $(this).attr(SELECTED_EXTERNAL_LINK_KEY);
 | 
				
			||||||
@ -329,6 +351,7 @@ function init() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    $.fn.setSelectedExternalLink = function (externalLink) {
 | 
					    $.fn.setSelectedExternalLink = function (externalLink) {
 | 
				
			||||||
        if (externalLink) {
 | 
					        if (externalLink) {
 | 
				
			||||||
 | 
					            // TODO: This doesn't seem to do anything with the external link, is it normal?
 | 
				
			||||||
            $(this)
 | 
					            $(this)
 | 
				
			||||||
                .closest(".input-group")
 | 
					                .closest(".input-group")
 | 
				
			||||||
                .find(".go-to-selected-note-button")
 | 
					                .find(".go-to-selected-note-button")
 | 
				
			||||||
							
								
								
									
										31
									
								
								src/public/app/types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								src/public/app/types.d.ts
									
									
									
									
										vendored
									
									
								
							@ -2,6 +2,7 @@ import type FNote from "./entities/fnote";
 | 
				
			|||||||
import type { BackendModule, i18n } from "i18next";
 | 
					import type { BackendModule, i18n } from "i18next";
 | 
				
			||||||
import type { Froca } from "./services/froca-interface";
 | 
					import type { Froca } from "./services/froca-interface";
 | 
				
			||||||
import type { HttpBackendOptions } from "i18next-http-backend";
 | 
					import type { HttpBackendOptions } from "i18next-http-backend";
 | 
				
			||||||
 | 
					import { Suggestion } from "./services/note_autocomplete.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ElectronProcess {
 | 
					interface ElectronProcess {
 | 
				
			||||||
    type: string;
 | 
					    type: string;
 | 
				
			||||||
@ -42,7 +43,7 @@ type RequireMethod = (moduleName: string) => any;
 | 
				
			|||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
    interface Window {
 | 
					    interface Window {
 | 
				
			||||||
        logError(message: string);
 | 
					        logError(message: string);
 | 
				
			||||||
        logInfo(message: string);
 | 
					        logInfo(message: string);        
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
        process?: ElectronProcess;
 | 
					        process?: ElectronProcess;
 | 
				
			||||||
        glob?: CustomGlobals;
 | 
					        glob?: CustomGlobals;
 | 
				
			||||||
@ -53,23 +54,36 @@ declare global {
 | 
				
			|||||||
        hint?: boolean;
 | 
					        hint?: boolean;
 | 
				
			||||||
        openOnFocus?: boolean;
 | 
					        openOnFocus?: boolean;
 | 
				
			||||||
        minLength?: number;
 | 
					        minLength?: number;
 | 
				
			||||||
        tabAutocomplete?: boolean
 | 
					        tabAutocomplete?: boolean;
 | 
				
			||||||
 | 
					        autoselect?: boolean;
 | 
				
			||||||
 | 
					        dropdownMenuContainer?: HTMLElement;
 | 
				
			||||||
 | 
					        debug?: boolean;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    type AutoCompleteCallback = (values: AutoCompleteCallbackArgs[]) => void;
 | 
					    type AutoCompleteCallback = (values: AutoCompleteCallbackArg[]) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    interface AutoCompleteArg {
 | 
					    interface AutoCompleteArg {
 | 
				
			||||||
        displayKey: "name" | "value";
 | 
					        displayKey: "name" | "value" | "notePathTitle";
 | 
				
			||||||
        cache: boolean;
 | 
					        cache: boolean;
 | 
				
			||||||
        source: (term: string, cb: AutoCompleteCallback) => void
 | 
					        source: (term: string, cb: AutoCompleteCallback) => void,
 | 
				
			||||||
 | 
					        templates: {
 | 
				
			||||||
 | 
					            suggestion: (suggestion: Suggestion) => string | undefined
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    interface JQuery {
 | 
					    interface JQuery {
 | 
				
			||||||
        autocomplete: (action: "close" | "open" | "destroy" | AutoCompleteConfig, args?: AutoCompleteArg[]) => void;        
 | 
					        autocomplete: (action?: "close" | "open" | "destroy" | "val" | AutoCompleteConfig, args?: AutoCompleteArg[] | string) => JQuery<?>;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        getSelectedNotePath(): string | undefined;
 | 
				
			||||||
 | 
					        getSelectedNoteId(): string | null;
 | 
				
			||||||
 | 
					        setSelectedNotePath(notePath: string | null | undefined);
 | 
				
			||||||
 | 
					        getSelectedExternalLink(this: HTMLElement): string | undefined;
 | 
				
			||||||
 | 
					        setSelectedExternalLink(externalLink: string | null | undefined);
 | 
				
			||||||
 | 
					        setNote(noteId: string);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var logError: (message: string) => void;
 | 
					    var logError: (message: string) => void;
 | 
				
			||||||
    var logInfo: (message: string) => void;
 | 
					    var logInfo: (message: string) => void;    
 | 
				
			||||||
    var glob: CustomGlobals;
 | 
					    var glob: CustomGlobals;
 | 
				
			||||||
    var require: RequireMethod;
 | 
					    var require: RequireMethod;
 | 
				
			||||||
    var __non_webpack_require__: RequireMethod | undefined;
 | 
					    var __non_webpack_require__: RequireMethod | undefined;
 | 
				
			||||||
@ -92,4 +106,7 @@ declare global {
 | 
				
			|||||||
    }) => {
 | 
					    }) => {
 | 
				
			||||||
        destroy();
 | 
					        destroy();
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    var renderMathInElement: (element: HTMLElement, options: {
 | 
				
			||||||
 | 
					        trust: boolean;
 | 
				
			||||||
 | 
					    }) => void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user