From 650a116193aec5aba670f204d04b70c027b000d1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 19 Dec 2024 22:06:42 +0200 Subject: [PATCH] chore(client/ts): port services/frontend_script_api --- src/public/app/components/app_context.ts | 12 +- ...d_script_api.js => frontend_script_api.ts} | 906 +++++++++--------- src/public/app/services/spaced_update.ts | 2 +- src/public/app/types.d.ts | 4 +- 4 files changed, 476 insertions(+), 448 deletions(-) rename src/public/app/services/{frontend_script_api.js => frontend_script_api.ts} (65%) diff --git a/src/public/app/components/app_context.ts b/src/public/app/components/app_context.ts index 6372d43ce..783373023 100644 --- a/src/public/app/components/app_context.ts +++ b/src/public/app/components/app_context.ts @@ -14,6 +14,7 @@ import MainTreeExecutors from "./main_tree_executors.js"; import toast from "../services/toast.js"; import ShortcutComponent from "./shortcut_component.js"; import { t, initLocale } from "../services/i18n.js"; +import NoteDetailWidget from "../widgets/note_detail.js"; interface Layout { getRootWidget: (appContext: AppContext) => RootWidget; @@ -27,11 +28,18 @@ interface BeforeUploadListener extends Component { beforeUnloadEvent(): boolean; } -interface TriggerData { +export type TriggerData = { noteId?: string; noteIds?: string[]; - messages?: unknown[]; + messages?: unknown[]; callback?: () => void; +} | { + ntxId: string; + notePath: string; +} | { + text: string; +} | { + callback: (value: NoteDetailWidget | PromiseLike) => void } class AppContext extends Component { diff --git a/src/public/app/services/frontend_script_api.js b/src/public/app/services/frontend_script_api.ts similarity index 65% rename from src/public/app/services/frontend_script_api.js rename to src/public/app/services/frontend_script_api.ts index 5af37876a..b320518fe 100644 --- a/src/public/app/services/frontend_script_api.js +++ b/src/public/app/services/frontend_script_api.ts @@ -9,12 +9,17 @@ import dateNotesService from './date_notes.js'; import searchService from './search.js'; import RightPanelWidget from '../widgets/right_panel_widget.js'; import ws from "./ws.js"; -import appContext from "../components/app_context.js"; +import appContext, { TriggerData } from "../components/app_context.js"; import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js"; import BasicWidget from "../widgets/basic_widget.js"; import SpacedUpdate from "./spaced_update.js"; import shortcutService from "./shortcuts.js"; import dialogService from "./dialog.js"; +import FNote from '../entities/fnote.js'; +import { t } from './i18n.js'; +import NoteContext from '../components/note_context.js'; +import NoteDetailWidget from '../widgets/note_detail.js'; +import Component from '../components/component.js'; /** @@ -28,71 +33,436 @@ import dialogService from "./dialog.js"; * @var {FrontendScriptApi} api */ +interface AddToToolbarOpts { + title: string; + /** callback handling the click on the button */ + action: () => void; + /** id of the button, used to identify the old instances of this button to be replaced + * ID is optional because of BC, but not specifying it is deprecated. ID can be alphanumeric only. */ + id: string; + /** name of the boxicon to be used (e.g. "time" for "bx-time" icon) */ + icon: string; + /** keyboard shortcut for the button, e.g. "alt+t" */ + shortcut: string; +} + +// TODO: Deduplicate me with the server. +interface ExecResult { + success: boolean; + executionResult: unknown; + error?: string; +} + +interface Entity { + noteId: string; +} + +type Func = ((...args: unknown[]) => unknown) | string; + +interface Api { + /** + * Container of all the rendered script content + * */ + $container: JQuery | null; + + /** + * Note where the script started executing, i.e., the (event) entrypoint of the current script execution. + */ + startNote: FNote; + + /** + * Note where the script is currently executing, i.e. the note where the currently executing source code is written. + */ + currentNote: FNote; + + /** + * Entity whose event triggered this execution. + */ + originEntity: unknown | null; + + /** + * day.js library for date manipulation. + * See {@link https://day.js.org} for documentation + * @see https://day.js.org + */ + dayjs: typeof window.dayjs; + + RightPanelWidget: typeof RightPanelWidget; + NoteContextAwareWidget: typeof NoteContextAwareWidget; + BasicWidget: typeof BasicWidget; + + /** + * Activates note in the tree and in the note detail. + * + * @param notePath (or noteId) + */ + activateNote(notePath: string): Promise; + + /** + * Activates newly created note. Compared to this.activateNote() also makes sure that frontend has been fully synced. + * + * @param notePath (or noteId) + */ + activateNewNote(notePath: string): Promise; + + /** + * Open a note in a new tab. + * + * @method + * @param notePath (or noteId) + * @param activate - set to true to activate the new tab, false to stay on the current tab + */ + openTabWithNote(notePath: string, activate: boolean): Promise; + + /** + * Open a note in a new split. + * + * @param notePath (or noteId) + * @param activate - set to true to activate the new split, false to stay on the current split + */ + openSplitWithNote(notePath: string, activate: boolean): Promise; + + /** + * Adds a new launcher to the launchbar. If the launcher (id) already exists, it will be updated. + * + * @method + * @deprecated you can now create/modify launchers in the top-left Menu -> Configure Launchbar + * for special needs there's also backend API's createOrUpdateLauncher() + */ + addButtonToToolbar(opts: AddToToolbarOpts): void; + + /** + * @private + */ + __runOnBackendInner(func: unknown, params: unknown[], transactional: boolean): unknown; + + /** + * Executes given anonymous function on the backend. + * Internally this serializes the anonymous function into string and sends it to backend via AJAX. + * Please make sure that the supplied function is synchronous. Only sync functions will work correctly + * with transaction management. If you really know what you're doing, you can call api.runAsyncOnBackendWithManualTransactionHandling() + * + * @method + * @param func - (synchronous) function to be executed on the backend + * @param params - list of parameters to the anonymous function to be sent to backend + * @returns return value of the executed function on the backend + */ + runOnBackend(func: Func, params: unknown[]): unknown; + + /** + * Executes given anonymous function on the backend. + * Internally this serializes the anonymous function into string and sends it to backend via AJAX. + * This function is meant for advanced needs where an async function is necessary. + * In this case, the automatic request-scoped transaction management is not applied, + * and you need to manually define transaction via api.transactional(). + * + * If you have a synchronous function, please use api.runOnBackend(). + * + * @method + * @param func - (synchronous) function to be executed on the backend + * @param params - list of parameters to the anonymous function to be sent to backend + * @returns return value of the executed function on the backend + */ + runAsyncOnBackendWithManualTransactionHandling(func: Func, params: unknown[]): unknown; + + /** + * This is a powerful search method - you can search by attributes and their values, e.g.: + * "#dateModified =* MONTH AND #log". See full documentation for all options at: https://triliumnext.github.io/Docs/Wiki/search.html + */ + searchForNotes(searchString: string): Promise; + + /** + * This is a powerful search method - you can search by attributes and their values, e.g.: + * "#dateModified =* MONTH AND #log". See full documentation for all options at: https://triliumnext.github.io/Docs/Wiki/search.html + */ + searchForNote(searchString: string): Promise; + + /** + * Returns note by given noteId. If note is missing from the cache, it's loaded. + */ + getNote(noteId: string): Promise; + + /** + * Returns list of notes. If note is missing from the cache, it's loaded. + * + * This is often used to bulk-fill the cache with notes which would have to be picked one by one + * otherwise (by e.g. createLink()) + * + * @param [silentNotFoundError] - don't report error if the note is not found + */ + getNotes(noteIds: string[], silentNotFoundError: boolean): Promise; + + /** + * Update frontend tree (note) cache from the backend. + */ + reloadNotes(noteIds: string[]): Promise; + + /** + * Instance name identifies particular Trilium instance. It can be useful for scripts + * if some action needs to happen on only one specific instance. + */ + getInstanceName(): string; + + /** + * @returns date in YYYY-MM-DD format + */ + formatDateISO: typeof utils.formatDateISO; + + parseDate: typeof utils.parseDate; + + /** + * Show an info toast message to the user. + */ + showMessage: typeof toastService.showMessage; + + /** + * Show an error toast message to the user. + */ + showError: typeof toastService.showError; + + /** + * Show an info dialog to the user. + */ + showInfoDialog: typeof dialogService.info; + + /** + * Show confirm dialog to the user. + * @returns promise resolving to true if the user confirmed + */ + showConfirmDialog: typeof dialogService.confirm; + + /** + * Show prompt dialog to the user. + * + * @returns promise resolving to the answer provided by the user + */ + showPromptDialog: typeof dialogService.prompt; + + /** + * Trigger command. This is a very low-level API which should be avoided if possible. + */ + triggerCommand(name: string, data: TriggerData): void; + + /** + * Trigger event. This is a very low-level API which should be avoided if possible. + */ + triggerEvent(name: string, data: TriggerData): void; + + /** + * Create a note link (jQuery object) for given note. + * + * @param {string} notePath (or noteId) + * @param {object} [params] + * @param {boolean} [params.showTooltip] - enable/disable tooltip on the link + * @param {boolean} [params.showNotePath] - show also whole note's path as part of the link + * @param {boolean} [params.showNoteIcon] - show also note icon before the title + * @param {string} [params.title] - custom link tile with note's title as default + * @param {string} [params.title=] - custom link tile with note's title as default + * @returns {jQuery} - jQuery element with the link (wrapped in ) + */ + createLink: typeof linkService.createLink; + + /** @deprecated - use api.createLink() instead */ + createNoteLink: typeof linkService.createLink; + + /** + * Adds given text to the editor cursor + * + * @param text - this must be clear text, HTML is not supported. + */ + addTextToActiveContextEditor(text: string): void; + + /** + * @returns active note (loaded into center pane) + */ + getActiveContextNote(): FNote; + + /** + * @returns returns active context (split) + */ + getActiveContext(): NoteContext; + + /** + * @returns returns active main context (first split in a tab, represents the tab as a whole) + */ + getActiveMainContext(): NoteContext; + + /** + * @returns returns all note contexts (splits) in all tabs + */ + getNoteContexts(): NoteContext[]; + + /** + * @returns returns all main contexts representing tabs + */ + getMainNoteContexts(): NoteContext[]; + + /** + * See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for documentation on the returned instance. + * + * @returns {Promise} instance of CKEditor + */ + getActiveContextTextEditor(): Promise; + + /** + * See https://codemirror.net/doc/manual.html#api + * + * @method + * @returns instance of CodeMirror + */ + getActiveContextCodeEditor(): Promise; + + /** + * Get access to the widget handling note detail. Methods like `getWidgetType()` and `getTypeWidget()` to get to the + * implementation of actual widget type. + */ + getActiveNoteDetailWidget(): Promise; + /** + * @returns returns a note path of active note or null if there isn't active note + */ + getActiveContextNotePath(): string | null; + + /** + * Returns component which owns the given DOM element (the nearest parent component in DOM tree) + * + * @method + * @param el DOM element + */ + getComponentByEl(el: HTMLElement): Component; + + /** + * @param {object} $el - jquery object on which to set up the tooltip + */ + setupElementTooltip: typeof noteTooltipService.setupElementTooltip; + + /** + * @param {boolean} protect - true to protect note, false to unprotect + */ + protectNote: typeof protectedSessionService.protectNote; + + /** + * @param noteId + * @param protect - true to protect subtree, false to unprotect + */ + protectSubTree: typeof protectedSessionService.protectNote; + + /** + * Returns date-note for today. If it doesn't exist, it is automatically created. + */ + getTodayNote: typeof dateNotesService.getTodayNote; + + /** + * Returns day note for a given date. If it doesn't exist, it is automatically created. + * + * @param date - e.g. "2019-04-29" + */ + getDayNote: typeof dateNotesService.getDayNote; + + /** + * Returns day note for the first date of the week of the given date. If it doesn't exist, it is automatically created. + * + * @param date - e.g. "2019-04-29" + */ + getWeekNote: typeof dateNotesService.getWeekNote; + + /** + * Returns month-note. If it doesn't exist, it is automatically created. + * + * @param month - e.g. "2019-04" + */ + getMonthNote: typeof dateNotesService.getMonthNote; + + /** + * Returns year-note. If it doesn't exist, it is automatically created. + * + * @method + * @param {string} year - e.g. "2019" + * @returns {Promise} + */ + getYearNote: typeof dateNotesService.getYearNote; + + /** + * Hoist note in the current tab. See https://triliumnext.github.io/Docs/Wiki/note-hoisting.html + * + * @param {string} noteId - set hoisted note. 'root' will effectively unhoist + */ + setHoistedNoteId(noteId: string): void; + + /** + * @param keyboardShortcut - e.g. "ctrl+shift+a" + * @param [namespace] specify namespace of the handler for the cases where call for bind may be repeated. + * If a handler with this ID exists, it's replaced by the new handler. + */ + bindGlobalShortcut: typeof shortcutService.bindGlobalShortcut; + + /** + * Trilium runs in a backend and frontend process, when something is changed on the backend from a script, + * frontend will get asynchronously synchronized. + * + * This method returns a promise which resolves once all the backend -> frontend synchronization is finished. + * Typical use case is when a new note has been created, we should wait until it is synced into frontend and only then activate it. + */ + waitUntilSynced: typeof ws.waitForMaxKnownEntityChangeId; + + /** + * This will refresh all currently opened notes which have included note specified in the parameter + * + * @param includedNoteId - noteId of the included note + */ + refreshIncludedNote(includedNoteId: string): void; + + /** + * Return randomly generated string of given length. This random string generation is NOT cryptographically secure. + * + * @method + * @param length of the string + * @returns random string + */ + randomString: typeof utils.randomString; + + /** + * @param size in bytes + * @return formatted string + */ + formatSize: typeof utils.formatSize; + + /** + * @param size in bytes + * @return formatted string + * @deprecated - use api.formatSize() + */ + formatNoteSize: typeof utils.formatSize; + + logMessages: Record; + logSpacedUpdates: Record; + + /** + * Log given message to the log pane in UI + */ + log(message: string): void; +} + /** *

This is the main frontend API interface for scripts. All the properties and methods are published in the "api" object * available in the JS frontend notes. You can use e.g. api.showMessage(api.startNote.title);

* * @constructor */ -function FrontendScriptApi(startNote, currentNote, originEntity = null, $container = null) { - /** - * Container of all the rendered script content - * @type {jQuery} - * */ +function FrontendScriptApi (this: Api, startNote: FNote, currentNote: FNote, originEntity: Entity | null = null, $container = null) { + this.$container = $container; - - /** - * Note where the script started executing, i.e., the (event) entrypoint of the current script execution. - * @type {FNote} - */ this.startNote = startNote; - - /** - * Note where the script is currently executing, i.e. the note where the currently executing source code is written. - * @type {FNote} - */ - this.currentNote = currentNote; - - /** - * Entity whose event triggered this execution. - * @type {object|null} - */ + this.currentNote = currentNote; this.originEntity = originEntity; - - /** - * day.js library for date manipulation. - * See {@link https://day.js.org} for documentation - * @see https://day.js.org - * @type {dayjs} - */ this.dayjs = dayjs; - - /** @type {RightPanelWidget} */ this.RightPanelWidget = RightPanelWidget; - - /** @type {NoteContextAwareWidget} */ this.NoteContextAwareWidget = NoteContextAwareWidget; - - /** @type {BasicWidget} */ this.BasicWidget = BasicWidget; - - /** - * Activates note in the tree and in the note detail. - * - * @method - * @param {string} notePath (or noteId) - * @returns {Promise} - */ + this.activateNote = async notePath => { await appContext.tabManager.getActiveContext().setNote(notePath); }; - /** - * Activates newly created note. Compared to this.activateNote() also makes sure that frontend has been fully synced. - * - * @param {string} notePath (or noteId) - * @returns {Promise} - */ this.activateNewNote = async notePath => { await ws.waitForMaxKnownEntityChangeId(); @@ -100,14 +470,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain await appContext.triggerEvent('focusAndSelectTitle'); }; - /** - * Open a note in a new tab. - * - * @method - * @param {string} notePath (or noteId) - * @param {boolean} activate - set to true to activate the new tab, false to stay on the current tab - * @returns {Promise} - */ + this.openTabWithNote = async (notePath, activate) => { await ws.waitForMaxKnownEntityChangeId(); @@ -117,15 +480,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain await appContext.triggerEvent('focusAndSelectTitle'); } }; - - /** - * Open a note in a new split. - * - * @method - * @param {string} notePath (or noteId) - * @param {boolean} activate - set to true to activate the new split, false to stay on the current split - * @returns {Promise} - */ + this.openSplitWithNote = async (notePath, activate) => { await ws.waitForMaxKnownEntityChangeId(); @@ -138,31 +493,19 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain await appContext.triggerEvent('focusAndSelectTitle'); } }; - - /** - * Adds a new launcher to the launchbar. If the launcher (id) already exists, it will be updated. - * - * @method - * @deprecated you can now create/modify launchers in the top-left Menu -> Configure Launchbar - * for special needs there's also backend API's createOrUpdateLauncher() - * @param {object} opts - * @param {string} opts.title - * @param {function} opts.action - callback handling the click on the button - * @param {string} [opts.id] - id of the button, used to identify the old instances of this button to be replaced - * ID is optional because of BC, but not specifying it is deprecated. ID can be alphanumeric only. - * @param {string} [opts.icon] - name of the boxicon to be used (e.g. "time" for "bx-time" icon) - * @param {string} [opts.shortcut] - keyboard shortcut for the button, e.g. "alt+t" - */ + this.addButtonToToolbar = async opts => { console.warn("api.addButtonToToolbar() has been deprecated since v0.58 and may be removed in the future. Use Menu -> Configure Launchbar to create/update launchers instead."); const {action, ...reqBody} = opts; - reqBody.action = action.toString(); - - await server.put('special-notes/api-script-launcher', reqBody); + + await server.put('special-notes/api-script-launcher', { + action: action.toString(), + ...reqBody + }); }; - function prepareParams(params) { + function prepareParams(params: unknown[]) { if (!params) { return params; } @@ -177,15 +520,13 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain }); } - /** - * @private - */ + this.__runOnBackendInner = async (func, params, transactional) => { if (typeof func === "function") { func = func.toString(); } - const ret = await server.post('script/exec', { + const ret = await server.post('script/exec', { script: func, params: prepareParams(params), startNoteId: startNote.noteId, @@ -204,364 +545,91 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain } } - /** - * Executes given anonymous function on the backend. - * Internally this serializes the anonymous function into string and sends it to backend via AJAX. - * Please make sure that the supplied function is synchronous. Only sync functions will work correctly - * with transaction management. If you really know what you're doing, you can call api.runAsyncOnBackendWithManualTransactionHandling() - * - * @method - * @param {function|string} func - (synchronous) function to be executed on the backend - * @param {Array.} params - list of parameters to the anonymous function to be sent to backend - * @returns {Promise<*>} return value of the executed function on the backend - */ this.runOnBackend = async (func, params = []) => { - if (func?.constructor.name === "AsyncFunction" || func?.startsWith?.("async ")) { + if (func?.constructor.name === "AsyncFunction" || (typeof func === "string" && func?.startsWith?.("async "))) { toastService.showError(t("frontend_script_api.async_warning")); } return await this.__runOnBackendInner(func, params, true); }; - /** - * Executes given anonymous function on the backend. - * Internally this serializes the anonymous function into string and sends it to backend via AJAX. - * This function is meant for advanced needs where an async function is necessary. - * In this case, the automatic request-scoped transaction management is not applied, - * and you need to manually define transaction via api.transactional(). - * - * If you have a synchronous function, please use api.runOnBackend(). - * - * @method - * @param {function|string} func - (synchronous) function to be executed on the backend - * @param {Array.} params - list of parameters to the anonymous function to be sent to backend - * @returns {Promise<*>} return value of the executed function on the backend - */ + this.runAsyncOnBackendWithManualTransactionHandling = async (func, params = []) => { - if (func?.constructor.name === "Function" || func?.startsWith?.("function")) { + if (func?.constructor.name === "Function" || (typeof func === "string" && func?.startsWith?.("function"))) { toastService.showError(t("frontend_script_api.sync_warning")); } return await this.__runOnBackendInner(func, params, false); }; - - /** - * This is a powerful search method - you can search by attributes and their values, e.g.: - * "#dateModified =* MONTH AND #log". See full documentation for all options at: https://triliumnext.github.io/Docs/Wiki/search.html - * - * @method - * @param {string} searchString - * @returns {Promise} - */ + this.searchForNotes = async searchString => { return await searchService.searchForNotes(searchString); }; - /** - * This is a powerful search method - you can search by attributes and their values, e.g.: - * "#dateModified =* MONTH AND #log". See full documentation for all options at: https://triliumnext.github.io/Docs/Wiki/search.html - * - * @method - * @param {string} searchString - * @returns {Promise} - */ + this.searchForNote = async searchString => { const notes = await this.searchForNotes(searchString); return notes.length > 0 ? notes[0] : null; }; - /** - * Returns note by given noteId. If note is missing from the cache, it's loaded. - ** - * @method - * @param {string} noteId - * @returns {Promise} - */ + this.getNote = async noteId => await froca.getNote(noteId); - - /** - * Returns list of notes. If note is missing from the cache, it's loaded. - * - * This is often used to bulk-fill the cache with notes which would have to be picked one by one - * otherwise (by e.g. createLink()) - * - * @method - * @param {string[]} noteIds - * @param {boolean} [silentNotFoundError] - don't report error if the note is not found - * @returns {Promise} - */ - this.getNotes = async (noteIds, silentNotFoundError = false) => await froca.getNotes(noteIds, silentNotFoundError); - - /** - * Update frontend tree (note) cache from the backend. - * - * @method - * @param {string[]} noteIds - */ - this.reloadNotes = async noteIds => await froca.reloadNotes(noteIds); - - /** - * Instance name identifies particular Trilium instance. It can be useful for scripts - * if some action needs to happen on only one specific instance. - * - * @method - * @returns {string} - */ - this.getInstanceName = () => window.glob.instanceName; - - /** - * @method - * @param {Date} date - * @returns {string} date in YYYY-MM-DD format - */ + this.getNotes = async (noteIds, silentNotFoundError = false) => await froca.getNotes(noteIds, silentNotFoundError); + this.reloadNotes = async noteIds => await froca.reloadNotes(noteIds); + this.getInstanceName = () => window.glob.instanceName; this.formatDateISO = utils.formatDateISO; - - /** - * @method - * @param {string} str - * @returns {Date} parsed object - */ this.parseDate = utils.parseDate; - /** - * Show an info toast message to the user. - * - * @method - * @param {string} message - */ this.showMessage = toastService.showMessage; - - /** - * Show an error toast message to the user. - * - * @method - * @param {string} message - */ this.showError = toastService.showError; - - /** - * Show an info dialog to the user. - * - * @method - * @param {string} message - * @returns {Promise} - */ - this.showInfoDialog = dialogService.info; - - /** - * Show confirm dialog to the user. - * - * @method - * @param {string} message - * @returns {Promise} promise resolving to true if the user confirmed - */ + this.showInfoDialog = dialogService.info; this.showConfirmDialog = dialogService.confirm; - /** - * Show prompt dialog to the user. - * - * @method - * @param {object} props - * @param {string} props.title - * @param {string} props.message - * @param {string} props.defaultValue - * @returns {Promise} promise resolving to the answer provided by the user - */ + this.showPromptDialog = dialogService.prompt; - - /** - * Trigger command. This is a very low-level API which should be avoided if possible. - * - * @method - * @param {string} name - * @param {object} data - */ - this.triggerCommand = (name, data) => appContext.triggerCommand(name, data); - - /** - * Trigger event. This is a very low-level API which should be avoided if possible. - * - * @method - * @param {string} name - * @param {object} data - */ + + this.triggerCommand = (name, data) => appContext.triggerCommand(name, data); this.triggerEvent = (name, data) => appContext.triggerEvent(name, data); - /** - * Create a note link (jQuery object) for given note. - * - * @method - * @param {string} notePath (or noteId) - * @param {object} [params] - * @param {boolean} [params.showTooltip=true] - enable/disable tooltip on the link - * @param {boolean} [params.showNotePath=false] - show also whole note's path as part of the link - * @param {boolean} [params.showNoteIcon=false] - show also note icon before the title - * @param {string} [params.title] - custom link tile with note's title as default - * @param {string} [params.title=] - custom link tile with note's title as default - * @returns {jQuery} - jQuery element with the link (wrapped in ) - */ + this.createLink = linkService.createLink; - - /** @deprecated - use api.createLink() instead */ this.createNoteLink = linkService.createLink; - - /** - * Adds given text to the editor cursor - * - * @method - * @param {string} text - this must be clear text, HTML is not supported. - */ + this.addTextToActiveContextEditor = text => appContext.triggerCommand('addTextToActiveEditor', {text}); - /** - * @method - * @returns {FNote} active note (loaded into center pane) - */ this.getActiveContextNote = () => appContext.tabManager.getActiveContextNote(); - - /** - * @method - * @returns {NoteContext} - returns active context (split) - */ this.getActiveContext = () => appContext.tabManager.getActiveContext(); - - /** - * @method - * @returns {NoteContext} - returns active main context (first split in a tab, represents the tab as a whole) - */ this.getActiveMainContext = () => appContext.tabManager.getActiveMainContext(); - - /** - * @method - * @returns {NoteContext[]} - returns all note contexts (splits) in all tabs - */ - this.getNoteContexts = () => appContext.tabManager.getNoteContexts(); - - /** - * @method - * @returns {NoteContext[]} - returns all main contexts representing tabs - */ + + this.getNoteContexts = () => appContext.tabManager.getNoteContexts(); this.getMainNoteContexts = () => appContext.tabManager.getMainNoteContexts(); - /** - * See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for documentation on the returned instance. - * - * @method - * @returns {Promise} instance of CKEditor - */ - this.getActiveContextTextEditor = () => appContext.tabManager.getActiveContext()?.getTextEditor(); - - /** - * See https://codemirror.net/doc/manual.html#api - * - * @method - * @returns {Promise} instance of CodeMirror - */ + + this.getActiveContextTextEditor = () => appContext.tabManager.getActiveContext()?.getTextEditor(); this.getActiveContextCodeEditor = () => appContext.tabManager.getActiveContext()?.getCodeEditor(); - - /** - * Get access to the widget handling note detail. Methods like `getWidgetType()` and `getTypeWidget()` to get to the - * implementation of actual widget type. - * - * @method - * @returns {Promise} - */ - this.getActiveNoteDetailWidget = () => new Promise(resolve => appContext.triggerCommand('executeInActiveNoteDetailWidget', {callback: resolve})); - - /** - * @method - * @returns {Promise} returns a note path of active note or null if there isn't active note - */ + + this.getActiveNoteDetailWidget = () => new Promise(resolve => appContext.triggerCommand('executeInActiveNoteDetailWidget', {callback: resolve})); this.getActiveContextNotePath = () => appContext.tabManager.getActiveContextNotePath(); - - /** - * Returns component which owns the given DOM element (the nearest parent component in DOM tree) - * - * @method - * @param {Element} el - DOM element - * @returns {Component} - */ + this.getComponentByEl = el => appContext.getComponentByEl(el); - - /** - * @method - * @param {object} $el - jquery object on which to set up the tooltip - * @returns {Promise} - */ + this.setupElementTooltip = noteTooltipService.setupElementTooltip; - - /** - * @method - * @param {string} noteId - * @param {boolean} protect - true to protect note, false to unprotect - * @returns {Promise} - */ + this.protectNote = async (noteId, protect) => { await protectedSessionService.protectNote(noteId, protect, false); }; - - /** - * @method - * @param {string} noteId - * @param {boolean} protect - true to protect subtree, false to unprotect - * @returns {Promise} - */ + this.protectSubTree = async (noteId, protect) => { await protectedSessionService.protectNote(noteId, protect, true); }; - - /** - * Returns date-note for today. If it doesn't exist, it is automatically created. - * - * @method - * @returns {Promise} - */ - this.getTodayNote = dateNotesService.getTodayNote; - - /** - * Returns day note for a given date. If it doesn't exist, it is automatically created. - * - * @method - * @param {string} date - e.g. "2019-04-29" - * @returns {Promise} - */ + + this.getTodayNote = dateNotesService.getTodayNote; this.getDayNote = dateNotesService.getDayNote; - - /** - * Returns day note for the first date of the week of the given date. If it doesn't exist, it is automatically created. - * - * @method - * @param {string} date - e.g. "2019-04-29" - * @returns {Promise} - */ - this.getWeekNote = dateNotesService.getWeekNote; - - /** - * Returns month-note. If it doesn't exist, it is automatically created. - * - * @method - * @param {string} month - e.g. "2019-04" - * @returns {Promise} - */ + this.getWeekNote = dateNotesService.getWeekNote; this.getMonthNote = dateNotesService.getMonthNote; - - /** - * Returns year-note. If it doesn't exist, it is automatically created. - * - * @method - * @param {string} year - e.g. "2019" - * @returns {Promise} - */ this.getYearNote = dateNotesService.getYearNote; - /** - * Hoist note in the current tab. See https://triliumnext.github.io/Docs/Wiki/note-hoisting.html - * - * @method - * @param {string} noteId - set hoisted note. 'root' will effectively unhoist - * @returns {Promise} - */ this.setHoistedNoteId = (noteId) => { const activeNoteContext = appContext.tabManager.getActiveContext(); @@ -570,69 +638,19 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain } }; - /** - * @method - * @param {string} keyboardShortcut - e.g. "ctrl+shift+a" - * @param {function} handler - * @param {string} [namespace] - specify namespace of the handler for the cases where call for bind may be repeated. - * If a handler with this ID exists, it's replaced by the new handler. - * @returns {Promise} - */ this.bindGlobalShortcut = shortcutService.bindGlobalShortcut; - - /** - * Trilium runs in a backend and frontend process, when something is changed on the backend from a script, - * frontend will get asynchronously synchronized. - * - * This method returns a promise which resolves once all the backend -> frontend synchronization is finished. - * Typical use case is when a new note has been created, we should wait until it is synced into frontend and only then activate it. - * - * @method - * @returns {Promise} - */ + this.waitUntilSynced = ws.waitForMaxKnownEntityChangeId; - - /** - * This will refresh all currently opened notes which have included note specified in the parameter - * - * @param includedNoteId - noteId of the included note - * @returns {Promise} - */ + this.refreshIncludedNote = includedNoteId => appContext.triggerEvent('refreshIncludedNote', {noteId: includedNoteId}); - /** - * Return randomly generated string of given length. This random string generation is NOT cryptographically secure. - * - * @method - * @param {int} length of the string - * @returns {string} random string - */ + this.randomString = utils.randomString; - - /** - * @method - * @param {int} size in bytes - * @return {string} formatted string - */ this.formatSize = utils.formatSize; - - /** - * @method - * @param {int} size in bytes - * @return {string} formatted string - * @deprecated - use api.formatSize() - */ this.formatNoteSize = utils.formatSize; this.logMessages = {}; - this.logSpacedUpdates = {}; - - /** - * Log given message to the log pane in UI - * - * @param message - * @returns {void} - */ + this.logSpacedUpdates = {}; this.log = message => { const {noteId} = this.startNote; diff --git a/src/public/app/services/spaced_update.ts b/src/public/app/services/spaced_update.ts index 4ac0fdd23..991320423 100644 --- a/src/public/app/services/spaced_update.ts +++ b/src/public/app/services/spaced_update.ts @@ -1,4 +1,4 @@ -type Callback = () => Promise; +type Callback = () => Promise | void; export default class SpacedUpdate { private updater: Callback; diff --git a/src/public/app/types.d.ts b/src/public/app/types.d.ts index 4fb103b79..88f79b7eb 100644 --- a/src/public/app/types.d.ts +++ b/src/public/app/types.d.ts @@ -34,6 +34,7 @@ interface CustomGlobals { maxEntityChangeIdAtLoad: number; maxEntityChangeSyncIdAtLoad: number; assetPath: string; + instanceName: string; } type RequireMethod = (moduleName: string) => any; @@ -64,7 +65,7 @@ declare global { }; interface JQuery { - autocomplete: (action: "close" | "open" | "destroy" | AutoCompleteConfig, args?: AutoCompleteArg[]) => void; + autocomplete: (action: "close" | "open" | "destroy" | AutoCompleteConfig, args?: AutoCompleteArg[]) => void; } var logError: (message: string) => void; @@ -83,4 +84,5 @@ declare global { language: string }); }; + var dayjs: {}; }