mirror of
https://github.com/zadam/trilium.git
synced 2025-11-03 03:46:37 +01:00
chore(client/ts): port services/note_autocomplete
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user