mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 13:01:31 +08:00 
			
		
		
		
	Merge pull request #41 from TriliumNext/feature/typescript_backend_5
Convert backend to TypeScript (64% -> 67%)
This commit is contained in:
		
						commit
						4c69384b5d
					
				| @ -7,7 +7,7 @@ const compression = require('compression'); | ||||
| const sessionParser = require('./routes/session_parser.js'); | ||||
| const utils = require('./services/utils'); | ||||
| 
 | ||||
| require('./services/handlers.js'); | ||||
| require('./services/handlers'); | ||||
| require('./becca/becca_loader'); | ||||
| 
 | ||||
| const app = express(); | ||||
| @ -51,7 +51,7 @@ require('./services/backup'); | ||||
| // trigger consistency checks timer
 | ||||
| require('./services/consistency_checks'); | ||||
| 
 | ||||
| require('./services/scheduler.js'); | ||||
| require('./services/scheduler'); | ||||
| 
 | ||||
| if (utils.isElectron()) { | ||||
|     require('@electron/remote/main').initialize(); | ||||
|  | ||||
| @ -668,7 +668,7 @@ class BNote extends AbstractBeccaEntity<BNote> { | ||||
|      * @param name - relation name to filter | ||||
|      * @returns all note's relations (attributes with type relation), excluding inherited ones | ||||
|      */ | ||||
|     getOwnedRelations(name: string): BAttribute[] { | ||||
|     getOwnedRelations(name?: string | null): BAttribute[] { | ||||
|         return this.getOwnedAttributes(RELATION, name); | ||||
|     } | ||||
| 
 | ||||
| @ -1407,7 +1407,7 @@ class BNote extends AbstractBeccaEntity<BNote> { | ||||
|      * @param name - relation name | ||||
|      * @param value - relation value (noteId) | ||||
|      */ | ||||
|     setRelation(name: string, value: string) { | ||||
|     setRelation(name: string, value?: string) { | ||||
|         return this.setAttribute(RELATION, name, value); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| const scriptService = require('../../services/script.js'); | ||||
| const scriptService = require('../../services/script'); | ||||
| const attributeService = require('../../services/attributes'); | ||||
| const becca = require('../../becca/becca'); | ||||
| const syncService = require('../../services/sync'); | ||||
| @ -11,7 +11,7 @@ const sql = require('../../services/sql'); | ||||
| // this and does result.then().
 | ||||
| async function exec(req) { | ||||
|     try { | ||||
|         const {body} = req; | ||||
|         const { body } = req; | ||||
| 
 | ||||
|         const execute = body => scriptService.executeScript( | ||||
|             body.script, | ||||
| @ -115,7 +115,7 @@ function getRelationBundles(req) { | ||||
| 
 | ||||
| function getBundle(req) { | ||||
|     const note = becca.getNote(req.params.noteId); | ||||
|     const {script, params} = req.body; | ||||
|     const { script, params } = req.body; | ||||
| 
 | ||||
|     return scriptService.getScriptBundleForFrontend(note, script, params); | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| const log = require('../services/log'); | ||||
| const fileService = require('./api/files.js'); | ||||
| const scriptService = require('../services/script.js'); | ||||
| const scriptService = require('../services/script'); | ||||
| const cls = require('../services/cls'); | ||||
| const sql = require('../services/sql'); | ||||
| const becca = require('../becca/becca'); | ||||
|  | ||||
| @ -1,27 +1,39 @@ | ||||
| const log = require('./log'); | ||||
| const noteService = require('./notes'); | ||||
| const sql = require('./sql'); | ||||
| const utils = require('./utils'); | ||||
| const attributeService = require('./attributes'); | ||||
| const dateNoteService = require('./date_notes'); | ||||
| const treeService = require('./tree'); | ||||
| const config = require('./config'); | ||||
| const axios = require('axios'); | ||||
| const dayjs = require('dayjs'); | ||||
| const xml2js = require('xml2js'); | ||||
| const cloningService = require('./cloning'); | ||||
| const appInfo = require('./app_info'); | ||||
| const searchService = require('./search/services/search'); | ||||
| const SearchContext = require('./search/search_context'); | ||||
| const becca = require('../becca/becca'); | ||||
| const ws = require('./ws'); | ||||
| const SpacedUpdate = require('./spaced_update'); | ||||
| const specialNotesService = require('./special_notes'); | ||||
| const branchService = require('./branches'); | ||||
| const exportService = require('./export/zip'); | ||||
| const syncMutex = require('./sync_mutex'); | ||||
| const backupService = require('./backup'); | ||||
| const optionsService = require('./options'); | ||||
| import log = require('./log'); | ||||
| import noteService = require('./notes'); | ||||
| import sql = require('./sql'); | ||||
| import utils = require('./utils'); | ||||
| import attributeService = require('./attributes'); | ||||
| import dateNoteService = require('./date_notes'); | ||||
| import treeService = require('./tree'); | ||||
| import config = require('./config'); | ||||
| import axios = require('axios'); | ||||
| import dayjs = require('dayjs'); | ||||
| import xml2js = require('xml2js'); | ||||
| import cloningService = require('./cloning'); | ||||
| import appInfo = require('./app_info'); | ||||
| import searchService = require('./search/services/search'); | ||||
| import SearchContext = require('./search/search_context'); | ||||
| import becca = require('../becca/becca'); | ||||
| import ws = require('./ws'); | ||||
| import SpacedUpdate = require('./spaced_update'); | ||||
| import specialNotesService = require('./special_notes'); | ||||
| import branchService = require('./branches'); | ||||
| import exportService = require('./export/zip'); | ||||
| import syncMutex = require('./sync_mutex'); | ||||
| import backupService = require('./backup'); | ||||
| import optionsService = require('./options'); | ||||
| import BNote = require('../becca/entities/bnote'); | ||||
| import AbstractBeccaEntity = require('../becca/entities/abstract_becca_entity'); | ||||
| import BBranch = require('../becca/entities/bbranch'); | ||||
| import BAttribute = require('../becca/entities/battribute'); | ||||
| import BAttachment = require('../becca/entities/battachment'); | ||||
| import BRevision = require('../becca/entities/brevision'); | ||||
| import BEtapiToken = require('../becca/entities/betapi_token'); | ||||
| import BOption = require('../becca/entities/boption'); | ||||
| import { AttributeRow, AttributeType, NoteType } from '../becca/entities/rows'; | ||||
| import Becca from '../becca/becca-interface'; | ||||
| import { NoteParams } from './note-interface'; | ||||
| import { ApiParams } from './backend_script_api_interface'; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
| @ -35,139 +47,372 @@ const optionsService = require('./options'); | ||||
|  * @var {BackendScriptApi} api | ||||
|  */ | ||||
| 
 | ||||
| /** | ||||
|  * <p>This is the main backend API interface for scripts. All the properties and methods are published in the "api" object | ||||
|  * available in the JS backend notes. You can use e.g. <code>api.log(api.startNote.title);</code></p> | ||||
|  * | ||||
|  * @constructor | ||||
|  */ | ||||
| function BackendScriptApi(currentNote, apiParams) { | ||||
| interface SearchParams { | ||||
|     includeArchivedNotes?: boolean; | ||||
|     ignoreHoistedNote?: boolean; | ||||
| } | ||||
| 
 | ||||
| interface NoteAndBranch { | ||||
|     note: BNote; | ||||
|     /** object having "note" and "branch" keys representing respective objects */ | ||||
|     branch: BBranch; | ||||
| } | ||||
| 
 | ||||
| interface Api { | ||||
|     /** | ||||
|      * Note where the script started executing (entrypoint). | ||||
|      * As an analogy, in C this would be the file which contains the main() function of the current process. | ||||
|      * @type {BNote} | ||||
|      */ | ||||
|     this.startNote = apiParams.startNote; | ||||
|     startNote?: BNote; | ||||
| 
 | ||||
|     /** | ||||
|      * Note where the script is currently executing. This comes into play when your script is spread in multiple code | ||||
|      * notes, the script starts in "startNote", but then through function calls may jump into another note (currentNote). | ||||
|      * A similar concept in C would be __FILE__ | ||||
|      * Don't mix this up with the concept of active note. | ||||
|      * @type {BNote} | ||||
|      */ | ||||
|     this.currentNote = currentNote; | ||||
|     currentNote: BNote; | ||||
| 
 | ||||
|     /** | ||||
|      * Entity whose event triggered this execution | ||||
|      * @type {AbstractBeccaEntity} | ||||
|      */ | ||||
|     this.originEntity = apiParams.originEntity; | ||||
| 
 | ||||
|     for (const key in apiParams) { | ||||
|         this[key] = apiParams[key]; | ||||
|     } | ||||
|     originEntity?: AbstractBeccaEntity<any>; | ||||
|      | ||||
|     /** | ||||
|      * Axios library for HTTP requests. See {@link https://axios-http.com} for documentation
 | ||||
|      * @type {axios} | ||||
|      * @deprecated use native (browser compatible) fetch() instead | ||||
|      */ | ||||
|     this.axios = axios; | ||||
|     axios: typeof axios; | ||||
| 
 | ||||
|     /** | ||||
|      * day.js library for date manipulation. See {@link https://day.js.org} for documentation
 | ||||
|      * @type {dayjs} | ||||
|      */ | ||||
|     this.dayjs = dayjs; | ||||
|     dayjs: typeof dayjs; | ||||
| 
 | ||||
|     /** | ||||
|      * xml2js library for XML parsing. See {@link https://github.com/Leonidas-from-XIV/node-xml2js} for documentation
 | ||||
|      * @type {xml2js} | ||||
|      */ | ||||
|     this.xml2js = xml2js; | ||||
| 
 | ||||
|     xml2js: typeof xml2js; | ||||
|      | ||||
|     /** | ||||
|      * Instance name identifies particular Trilium instance. It can be useful for scripts | ||||
|      * if some action needs to happen on only one specific instance. | ||||
|      * | ||||
|      * @returns {string|null} | ||||
|      */ | ||||
|     this.getInstanceName = () => config.General ? config.General.instanceName : null; | ||||
|     getInstanceName(): string | null; | ||||
|      | ||||
|     getNote(noteId: string): BNote | null; | ||||
|     getBranch(branchId: string): BBranch | null; | ||||
|     getAttribute(attachmentId: string): BAttribute | null; | ||||
|     getAttachment(attachmentId: string): BAttachment | null; | ||||
|     getRevision(revisionId: string): BRevision | null; | ||||
|     getEtapiToken(etapiTokenId: string): BEtapiToken | null; | ||||
|     getEtapiTokens(): BEtapiToken[]; | ||||
|     getOption(optionName: string): BOption | null; | ||||
|     getOptions(): BOption[]; | ||||
|     getAttribute(attributeId: string): BAttribute | null; | ||||
|      | ||||
|     /** | ||||
|      * @method | ||||
|      * @param {string} noteId | ||||
|      * @returns {BNote|null} | ||||
|      * This is a powerful search method - you can search by attributes and their values, e.g.: | ||||
|      * "#dateModified =* MONTH AND #log". See {@link https://github.com/zadam/trilium/wiki/Search} for full documentation for all options
 | ||||
|      */ | ||||
|     this.getNote = noteId => becca.getNote(noteId); | ||||
| 
 | ||||
|     /** | ||||
|      * @method | ||||
|      * @param {string} branchId | ||||
|      * @returns {BBranch|null} | ||||
|      */ | ||||
|     this.getBranch = branchId => becca.getBranch(branchId); | ||||
| 
 | ||||
|     /** | ||||
|      * @method | ||||
|      * @param {string} attributeId | ||||
|      * @returns {BAttribute|null} | ||||
|      */ | ||||
|     this.getAttribute = attributeId => becca.getAttribute(attributeId); | ||||
| 
 | ||||
|     /** | ||||
|      * @method | ||||
|      * @param {string} attachmentId | ||||
|      * @returns {BAttachment|null} | ||||
|      */ | ||||
|     this.getAttachment = attachmentId => becca.getAttachment(attachmentId); | ||||
| 
 | ||||
|     /** | ||||
|      * @method | ||||
|      * @param {string} revisionId | ||||
|      * @returns {BRevision|null} | ||||
|      */ | ||||
|     this.getRevision = revisionId => becca.getRevision(revisionId); | ||||
| 
 | ||||
|     /** | ||||
|      * @method | ||||
|      * @param {string} etapiTokenId | ||||
|      * @returns {BEtapiToken|null} | ||||
|      */ | ||||
|     this.getEtapiToken = etapiTokenId => becca.getEtapiToken(etapiTokenId); | ||||
| 
 | ||||
|     /** | ||||
|      * @method | ||||
|      * @returns {BEtapiToken[]} | ||||
|      */ | ||||
|     this.getEtapiTokens = () => becca.getEtapiTokens(); | ||||
| 
 | ||||
|     /** | ||||
|      * @method | ||||
|      * @param {string} optionName | ||||
|      * @returns {BOption|null} | ||||
|      */ | ||||
|     this.getOption = optionName => becca.getOption(optionName); | ||||
| 
 | ||||
|     /** | ||||
|      * @method | ||||
|      * @returns {BOption[]} | ||||
|      */ | ||||
|     this.getOptions = () => optionsService.getOptions(); | ||||
| 
 | ||||
|     /** | ||||
|      * @method | ||||
|      * @param {string} attributeId | ||||
|      * @returns {BAttribute|null} | ||||
|      */ | ||||
|     this.getAttribute = attributeId => becca.getAttribute(attributeId); | ||||
|     searchForNotes(query: string, searchParams: SearchParams): BNote[]; | ||||
|      | ||||
|     /** | ||||
|      * This is a powerful search method - you can search by attributes and their values, e.g.: | ||||
|      * "#dateModified =* MONTH AND #log". See {@link https://github.com/zadam/trilium/wiki/Search} for full documentation for all options
 | ||||
|      * | ||||
|      * @method | ||||
|      * @param {string} query | ||||
|      * @param {Object} [searchParams] | ||||
|      * @returns {BNote[]} | ||||
|      */ | ||||
|     searchForNote(query: string, searchParams: SearchParams): BNote | null; | ||||
|      | ||||
|     /** | ||||
|      * Retrieves notes with given label name & value | ||||
|      * | ||||
|      * @param  name - attribute name | ||||
|      * @param  value - attribute value | ||||
|      */ | ||||
|     getNotesWithLabel(name: string, value?: string): BNote[]; | ||||
| 
 | ||||
|     /** | ||||
|      * Retrieves first note with given label name & value | ||||
|      * | ||||
|      * @param name - attribute name | ||||
|      * @param value - attribute value | ||||
|      */ | ||||
|     getNoteWithLabel(name: string, value?: string): BNote | null; | ||||
| 
 | ||||
|     /** | ||||
|      * If there's no branch between note and parent note, create one. Otherwise, do nothing. Returns the new or existing branch. | ||||
|      * | ||||
|      * @param prefix - if branch is created between note and parent note, set this prefix | ||||
|      */ | ||||
|     ensureNoteIsPresentInParent(noteId: string, parentNoteId: string, prefix: string): { | ||||
|         branch: BBranch | null | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * If there's a branch between note and parent note, remove it. Otherwise, do nothing. | ||||
|      */ | ||||
|     ensureNoteIsAbsentFromParent(noteId: string, parentNoteId: string): void; | ||||
| 
 | ||||
|     /** | ||||
|      * Based on the value, either create or remove branch between note and parent note. | ||||
|      * | ||||
|      * @param present - true if we want the branch to exist, false if we want it gone | ||||
|      * @param prefix - if branch is created between note and parent note, set this prefix | ||||
|      */ | ||||
|     toggleNoteInParent(present: true, noteId: string, parentNoteId: string, prefix: string): void; | ||||
| 
 | ||||
|     /** | ||||
|      * Create text note. See also createNewNote() for more options. | ||||
|      */ | ||||
|     createTextNote(parentNoteId: string, title: string, content: string): NoteAndBranch; | ||||
| 
 | ||||
|     /** | ||||
|      * Create data note - data in this context means object serializable to JSON. Created note will be of type 'code' and | ||||
|      * JSON MIME type. See also createNewNote() for more options. | ||||
|      */ | ||||
|     createDataNote(parentNoteId: string, title: string, content: {}): NoteAndBranch; | ||||
| 
 | ||||
|     /** | ||||
|      * @returns object contains newly created entities note and branch | ||||
|      */ | ||||
|     createNewNote(params: NoteParams): NoteAndBranch; | ||||
| 
 | ||||
|     /** | ||||
|      * @deprecated please use createTextNote() with similar API for simpler use cases or createNewNote() for more complex needs | ||||
|      * @param parentNoteId - create new note under this parent | ||||
|      * @returns object contains newly created entities note and branch | ||||
|      */ | ||||
|     createNote(parentNoteId: string, title: string, content: string, extraOptions: Omit<NoteParams, "title" | "content" | "type" | "parentNoteId"> & { | ||||
|         /** should the note be JSON */ | ||||
|         json?: boolean; | ||||
|         attributes?: AttributeRow[] | ||||
|     }): NoteAndBranch; | ||||
| 
 | ||||
|     logMessages: Record<string, string[]>; | ||||
|     logSpacedUpdates: Record<string, SpacedUpdate>; | ||||
| 
 | ||||
|     /** | ||||
|      * Log given message to trilium logs and log pane in UI | ||||
|      */ | ||||
|     log(message: string): void; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns root note of the calendar. | ||||
|      */ | ||||
|     getRootCalendarNote(): BNote | null; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns day note for given date. If such note doesn't exist, it is created. | ||||
|      * | ||||
|      * @method | ||||
|      * @param date in YYYY-MM-DD format | ||||
|      * @param rootNote - specify calendar root note, normally leave empty to use the default calendar | ||||
|      */ | ||||
|     getDayNote(date: string, rootNote?: BNote): BNote | null; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns today's day note. If such note doesn't exist, it is created. | ||||
|      *  | ||||
|      * @param rootNote specify calendar root note, normally leave empty to use the default calendar | ||||
|      */ | ||||
|     getTodayNote(rootNote?: BNote): BNote | null; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns note for the first date of the week of the given date. | ||||
|      * | ||||
|      * @param date in YYYY-MM-DD format | ||||
|      * @param rootNote - specify calendar root note, normally leave empty to use the default calendar | ||||
|      */ | ||||
|     getWeekNote(date: string, options: { | ||||
|         // TODO: Deduplicate type with date_notes.ts once ES modules are added.
 | ||||
|         /** either "monday" (default) or "sunday" */ | ||||
|         startOfTheWeek: "monday" | "sunday"; | ||||
|     }, rootNote: BNote): BNote | null; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns month note for given date. If such a note doesn't exist, it is created. | ||||
|      * | ||||
|      * @param date in YYYY-MM format | ||||
|      * @param rootNote - specify calendar root note, normally leave empty to use the default calendar | ||||
|      */ | ||||
|     getMonthNote(date: string, rootNote: BNote): BNote | null; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns year note for given year. If such a note doesn't exist, it is created. | ||||
|      * | ||||
|      * @param year in YYYY format | ||||
|      * @param rootNote - specify calendar root note, normally leave empty to use the default calendar | ||||
|      */ | ||||
|     getYearNote(year: string, rootNote?: BNote): BNote | null; | ||||
| 
 | ||||
|     /** | ||||
|      * Sort child notes of a given note. | ||||
|      */ | ||||
|     sortNotes(parentNoteId: string, sortConfig: { | ||||
|         /** 'title', 'dateCreated', 'dateModified' or a label name | ||||
|           * See {@link https://github.com/zadam/trilium/wiki/Sorting} for details. */
 | ||||
|         sortBy?: string; | ||||
|         reverse?: boolean; | ||||
|         foldersFirst?: boolean; | ||||
|     }): void; | ||||
| 
 | ||||
|     /** | ||||
|      * This method finds note by its noteId and prefix and either sets it to the given parentNoteId | ||||
|      * or removes the branch (if parentNoteId is not given). | ||||
|      * | ||||
|      * This method looks similar to toggleNoteInParent() but differs because we're looking up branch by prefix. | ||||
|      * | ||||
|      * @deprecated this method is pretty confusing and serves specialized purpose only | ||||
|      */ | ||||
|     setNoteToParent(noteId: string, prefix: string, parentNoteId: string | null): void; | ||||
| 
 | ||||
|     /** | ||||
|      * This functions wraps code which is supposed to be running in transaction. If transaction already | ||||
|      * exists, then we'll use that transaction. | ||||
|      * | ||||
|      * @param func | ||||
|      * @returns result of func callback | ||||
|      */ | ||||
|     transactional(func: () => void): any; | ||||
| 
 | ||||
|     /** | ||||
|      * Return randomly generated string of given length. This random string generation is NOT cryptographically secure. | ||||
|      * | ||||
|      * @param length of the string | ||||
|      * @returns random string | ||||
|      */ | ||||
|     randomString(length: number): string; | ||||
| 
 | ||||
|     /** | ||||
|      * @param to escape | ||||
|      * @returns escaped string | ||||
|      */ | ||||
|     escapeHtml(string: string): string; | ||||
| 
 | ||||
|     /** | ||||
|      * @param string to unescape | ||||
|      * @returns unescaped string | ||||
|      */ | ||||
|     unescapeHtml(string: string): string; | ||||
| 
 | ||||
|     /** | ||||
|      * sql | ||||
|      * @type {module:sql} | ||||
|      */ | ||||
|     sql: any; | ||||
| 
 | ||||
|     getAppInfo(): typeof appInfo; | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a new launcher to the launchbar. If the launcher (id) already exists, it will be updated. | ||||
|      */ | ||||
|     createOrUpdateLauncher(opts: { | ||||
|         /** id of the launcher, only alphanumeric at least 6 characters long */ | ||||
|         id: string; | ||||
|         /** one of | ||||
|          * - "note" - activating the launcher will navigate to the target note (specified in targetNoteId param) | ||||
|          * - "script" -  activating the launcher will execute the script (specified in scriptNoteId param) | ||||
|          * - "customWidget" - the launcher will be rendered with a custom widget (specified in widgetNoteId param) | ||||
|          */ | ||||
|         type: "note" | "script" | "customWidget"; | ||||
|         title: string; | ||||
|         /** if true, will be created in the "Visible launchers", otherwise in "Available launchers" */ | ||||
|         isVisible: boolean; | ||||
|         /** name of the boxicon to be used (e.g. "bx-time") */ | ||||
|         icon: string; | ||||
|         /** will activate the target note/script upon pressing, e.g. "ctrl+e" */ | ||||
|         keyboardShortcut: string; | ||||
|         /** for type "note" */ | ||||
|         targetNoteId: string; | ||||
|         /** for type "script" */ | ||||
|         scriptNoteId: string; | ||||
|         /** for type "customWidget" */ | ||||
|         widgetNoteId?: string; | ||||
|     }): { note: BNote }; | ||||
| 
 | ||||
|     /** | ||||
|      * @param format - either 'html' or 'markdown' | ||||
|      */ | ||||
|     exportSubtreeToZipFile(noteId: string, format: "markdown" | "html", zipFilePath: string): Promise<void>; | ||||
| 
 | ||||
|     /** | ||||
|      * Executes given anonymous function on the frontend(s). | ||||
|      * Internally, this serializes the anonymous function into string and sends it to frontend(s) via WebSocket. | ||||
|      * Note that there can be multiple connected frontend instances (e.g. in different tabs). In such case, all | ||||
|      * instances execute the given function. | ||||
|      * | ||||
|      * @param script - script to be executed on the frontend | ||||
|      * @param params - list of parameters to the anonymous function to be sent to frontend | ||||
|      * @returns no return value is provided. | ||||
|      */ | ||||
|     runOnFrontend(script: () => void | string, params: []): void; | ||||
| 
 | ||||
|     /** | ||||
|      * Sync process can make data intermittently inconsistent. Scripts which require strong data consistency | ||||
|      * can use this function to wait for a possible sync process to finish and prevent new sync process from starting | ||||
|      * while it is running. | ||||
|      * | ||||
|      * Because this is an async process, the inner callback doesn't have automatic transaction handling, so in case | ||||
|      * you need to make some DB changes, you need to surround your call with api.transactional(...) | ||||
|      * | ||||
|      * @param callback - function to be executed while sync process is not running | ||||
|      * @returns resolves once the callback is finished (callback is awaited) | ||||
|      */ | ||||
|     runOutsideOfSync(callback: () => void): Promise<void>; | ||||
| 
 | ||||
|     /** | ||||
|      * @param backupName - If the backupName is e.g. "now", then the backup will be written to "backup-now.db" file | ||||
|      * @returns resolves once the backup is finished | ||||
|      */ | ||||
|     backupNow(backupName: string): Promise<string>; | ||||
| 
 | ||||
|      /** | ||||
|      * This object contains "at your risk" and "no BC guarantees" objects for advanced use cases. | ||||
|      */ | ||||
|     __private: { | ||||
|         /** provides access to the backend in-memory object graph, see {@link https://github.com/zadam/trilium/blob/master/src/becca/becca.js} */ | ||||
|         becca: Becca; | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| // TODO: Convert to class.
 | ||||
| /** | ||||
|  * <p>This is the main backend API interface for scripts. All the properties and methods are published in the "api" object | ||||
|  * available in the JS backend notes. You can use e.g. <code>api.log(api.startNote.title);</code></p> | ||||
|  * | ||||
|  * @constructor | ||||
|  */ | ||||
| function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) { | ||||
|     this.startNote = apiParams.startNote; | ||||
|      | ||||
|     this.currentNote = currentNote; | ||||
|      | ||||
|     this.originEntity = apiParams.originEntity; | ||||
| 
 | ||||
|     for (const key in apiParams) { | ||||
|         (this as any)[key] = apiParams[key as keyof ApiParams]; | ||||
|     } | ||||
| 
 | ||||
|     this.axios = axios; | ||||
|     this.dayjs = dayjs; | ||||
|     this.xml2js = xml2js; | ||||
|     this.getInstanceName = () => config.General ? config.General.instanceName : null; | ||||
|     this.getNote = noteId => becca.getNote(noteId); | ||||
|     this.getBranch = branchId => becca.getBranch(branchId); | ||||
|     this.getAttribute = attributeId => becca.getAttribute(attributeId); | ||||
|     this.getAttachment = attachmentId => becca.getAttachment(attachmentId); | ||||
|     this.getRevision = revisionId => becca.getRevision(revisionId); | ||||
|     this.getEtapiToken = etapiTokenId => becca.getEtapiToken(etapiTokenId); | ||||
|     this.getEtapiTokens = () => becca.getEtapiTokens(); | ||||
|     this.getOption = optionName => becca.getOption(optionName); | ||||
|     this.getOptions = () => optionsService.getOptions(); | ||||
|     this.getAttribute = attributeId => becca.getAttribute(attributeId); | ||||
|      | ||||
|     this.searchForNotes = (query, searchParams = {}) => { | ||||
|         if (searchParams.includeArchivedNotes === undefined) { | ||||
|             searchParams.includeArchivedNotes = true; | ||||
| @ -183,83 +428,18 @@ function BackendScriptApi(currentNote, apiParams) { | ||||
|         return becca.getNotes(noteIds); | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * This is a powerful search method - you can search by attributes and their values, e.g.: | ||||
|      * "#dateModified =* MONTH AND #log". See {@link https://github.com/zadam/trilium/wiki/Search} for full documentation for all options
 | ||||
|      * | ||||
|      * @method | ||||
|      * @param {string} query | ||||
|      * @param {Object} [searchParams] | ||||
|      * @returns {BNote|null} | ||||
|      */ | ||||
|      | ||||
|     this.searchForNote = (query, searchParams = {}) => { | ||||
|         const notes = this.searchForNotes(query, searchParams); | ||||
| 
 | ||||
|         return notes.length > 0 ? notes[0] : null; | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * Retrieves notes with given label name & value | ||||
|      * | ||||
|      * @method | ||||
|      * @param {string} name - attribute name | ||||
|      * @param {string} [value] - attribute value | ||||
|      * @returns {BNote[]} | ||||
|      */ | ||||
|     this.getNotesWithLabel = attributeService.getNotesWithLabel;     | ||||
| 
 | ||||
|     /** | ||||
|      * Retrieves first note with given label name & value | ||||
|      * | ||||
|      * @method | ||||
|      * @param {string} name - attribute name | ||||
|      * @param {string} [value] - attribute value | ||||
|      * @returns {BNote|null} | ||||
|      */ | ||||
|     this.getNoteWithLabel = attributeService.getNoteWithLabel; | ||||
| 
 | ||||
|     /** | ||||
|      * If there's no branch between note and parent note, create one. Otherwise, do nothing. Returns the new or existing branch. | ||||
|      * | ||||
|      * @method | ||||
|      * @param {string} noteId | ||||
|      * @param {string} parentNoteId | ||||
|      * @param {string} prefix - if branch is created between note and parent note, set this prefix | ||||
|      * @returns {{branch: BBranch|null}} | ||||
|      */ | ||||
|     this.ensureNoteIsPresentInParent = cloningService.ensureNoteIsPresentInParent; | ||||
| 
 | ||||
|     /** | ||||
|      * If there's a branch between note and parent note, remove it. Otherwise, do nothing. | ||||
|      * | ||||
|      * @method | ||||
|      * @param {string} noteId | ||||
|      * @param {string} parentNoteId | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     this.ensureNoteIsAbsentFromParent = cloningService.ensureNoteIsAbsentFromParent; | ||||
| 
 | ||||
|     /** | ||||
|      * Based on the value, either create or remove branch between note and parent note. | ||||
|      * | ||||
|      * @method | ||||
|      * @param {boolean} present - true if we want the branch to exist, false if we want it gone | ||||
|      * @param {string} noteId | ||||
|      * @param {string} parentNoteId | ||||
|      * @param {string} prefix - if branch is created between note and parent note, set this prefix | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     this.toggleNoteInParent = cloningService.toggleNoteInParent; | ||||
| 
 | ||||
|     /** | ||||
|      * Create text note. See also createNewNote() for more options. | ||||
|      * | ||||
|      * @method | ||||
|      * @param {string} parentNoteId | ||||
|      * @param {string} title | ||||
|      * @param {string} content | ||||
|      * @returns {{note: BNote, branch: BBranch}} - object having "note" and "branch" keys representing respective objects | ||||
|      */ | ||||
|     this.createTextNote = (parentNoteId, title, content = '') => noteService.createNewNote({ | ||||
|         parentNoteId, | ||||
|         title, | ||||
| @ -267,16 +447,6 @@ function BackendScriptApi(currentNote, apiParams) { | ||||
|         type: 'text' | ||||
|     }); | ||||
| 
 | ||||
|     /** | ||||
|      * Create data note - data in this context means object serializable to JSON. Created note will be of type 'code' and | ||||
|      * JSON MIME type. See also createNewNote() for more options. | ||||
|      * | ||||
|      * @method | ||||
|      * @param {string} parentNoteId | ||||
|      * @param {string} title | ||||
|      * @param {object} content | ||||
|      * @returns {{note: BNote, branch: BBranch}} object having "note" and "branch" keys representing respective objects | ||||
|      */ | ||||
|     this.createDataNote = (parentNoteId, title, content = {}) => noteService.createNewNote({ | ||||
|         parentNoteId, | ||||
|         title, | ||||
| @ -285,52 +455,27 @@ function BackendScriptApi(currentNote, apiParams) { | ||||
|         mime: 'application/json' | ||||
|     }); | ||||
|      | ||||
|     /** | ||||
|      * @method | ||||
|      * | ||||
|      * @param {object} params | ||||
|      * @param {string} params.parentNoteId | ||||
|      * @param {string} params.title | ||||
|      * @param {string|Buffer} params.content | ||||
|      * @param {NoteType} params.type - text, code, file, image, search, book, relationMap, canvas, webView | ||||
|      * @param {string} [params.mime] - value is derived from default mimes for type | ||||
|      * @param {boolean} [params.isProtected=false] | ||||
|      * @param {boolean} [params.isExpanded=false] | ||||
|      * @param {string} [params.prefix=''] | ||||
|      * @param {int} [params.notePosition] - default is last existing notePosition in a parent + 10 | ||||
|      * @returns {{note: BNote, branch: BBranch}} object contains newly created entities note and branch | ||||
|      */ | ||||
|     this.createNewNote = noteService.createNewNote; | ||||
|      | ||||
|     /** | ||||
|      * @method | ||||
|      * @deprecated please use createTextNote() with similar API for simpler use cases or createNewNote() for more complex needs | ||||
|      * | ||||
|      * @param {string} parentNoteId - create new note under this parent | ||||
|      * @param {string} title | ||||
|      * @param {string} [content=""] | ||||
|      * @param {object} [extraOptions={}] | ||||
|      * @param {boolean} [extraOptions.json=false] - should the note be JSON | ||||
|      * @param {boolean} [extraOptions.isProtected=false] - should the note be protected | ||||
|      * @param {string} [extraOptions.type='text'] - note type | ||||
|      * @param {string} [extraOptions.mime='text/html'] - MIME type of the note | ||||
|      * @param {object[]} [extraOptions.attributes=[]] - attributes to be created for this note | ||||
|      * @param {AttributeType} extraOptions.attributes.type - attribute type - label, relation etc. | ||||
|      * @param {string} extraOptions.attributes.name - attribute name | ||||
|      * @param {string} [extraOptions.attributes.value] - attribute value | ||||
|      * @returns {{note: BNote, branch: BBranch}} object contains newly created entities note and branch | ||||
|      */ | ||||
|     this.createNote = (parentNoteId, title, content = "", extraOptions = {}) => { | ||||
|         extraOptions.parentNoteId = parentNoteId; | ||||
|         extraOptions.title = title; | ||||
| 
 | ||||
|     this.createNote = (parentNoteId, title, content = "", _extraOptions = {}) => { | ||||
|         const parentNote = becca.getNote(parentNoteId); | ||||
|         if (!parentNote) { | ||||
|             throw new Error(`Unable to find parent note with ID ${parentNote}.`); | ||||
|         } | ||||
| 
 | ||||
|         let extraOptions: NoteParams = { | ||||
|             ..._extraOptions, | ||||
|             content: "", | ||||
|             type: "text", | ||||
|             parentNoteId, | ||||
|             title | ||||
|         }; | ||||
| 
 | ||||
|         // code note type can be inherited, otherwise "text" is the default
 | ||||
|         extraOptions.type = parentNote.type === 'code' ? 'code' : 'text'; | ||||
|         extraOptions.mime = parentNote.type === 'code' ? parentNote.mime : 'text/html'; | ||||
| 
 | ||||
|         if (extraOptions.json) { | ||||
|         if (_extraOptions.json) { | ||||
|             extraOptions.content = JSON.stringify(content || {}, null, '\t'); | ||||
|             extraOptions.type = 'code'; | ||||
|             extraOptions.mime = 'application/json'; | ||||
| @ -342,7 +487,7 @@ function BackendScriptApi(currentNote, apiParams) { | ||||
|         return sql.transactional(() => { | ||||
|             const { note, branch } = noteService.createNewNote(extraOptions); | ||||
| 
 | ||||
|             for (const attr of extraOptions.attributes || []) { | ||||
|             for (const attr of _extraOptions.attributes || []) { | ||||
|                 attributeService.createAttribute({ | ||||
|                     noteId: note.noteId, | ||||
|                     type: attr.type, | ||||
| @ -359,16 +504,13 @@ function BackendScriptApi(currentNote, apiParams) { | ||||
|     this.logMessages = {}; | ||||
|     this.logSpacedUpdates = {}; | ||||
|      | ||||
|     /** | ||||
|      * Log given message to trilium logs and log pane in UI | ||||
|      * | ||||
|      * @method | ||||
|      * @param message | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     this.log = message => { | ||||
|         log.info(message); | ||||
| 
 | ||||
|         if (!this.startNote) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const { noteId } = this.startNote; | ||||
| 
 | ||||
|         this.logMessages[noteId] = this.logMessages[noteId] || []; | ||||
| @ -387,77 +529,13 @@ function BackendScriptApi(currentNote, apiParams) { | ||||
|         this.logSpacedUpdates[noteId].scheduleUpdate(); | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns root note of the calendar. | ||||
|      * | ||||
|      * @method | ||||
|      * @returns {BNote|null} | ||||
|      */ | ||||
|     this.getRootCalendarNote = dateNoteService.getRootCalendarNote; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns day note for given date. If such note doesn't exist, it is created. | ||||
|      * | ||||
|      * @method | ||||
|      * @param {string} date in YYYY-MM-DD format | ||||
|      * @param {BNote} [rootNote] - specify calendar root note, normally leave empty to use the default calendar | ||||
|      * @returns {BNote|null} | ||||
|      */ | ||||
|     this.getDayNote = dateNoteService.getDayNote; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns today's day note. If such note doesn't exist, it is created. | ||||
|      * | ||||
|      * @method | ||||
|      * @param {BNote} [rootNote] - specify calendar root note, normally leave empty to use the default calendar | ||||
|      * @returns {BNote|null} | ||||
|      */ | ||||
|     this.getTodayNote = dateNoteService.getTodayNote; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns note for the first date of the week of the given date. | ||||
|      * | ||||
|      * @method | ||||
|      * @param {string} date in YYYY-MM-DD format | ||||
|      * @param {object} [options] | ||||
|      * @param {string} [options.startOfTheWeek=monday] - either "monday" (default) or "sunday" | ||||
|      * @param {BNote} [rootNote] - specify calendar root note, normally leave empty to use the default calendar | ||||
|      * @returns {BNote|null} | ||||
|      */ | ||||
|     this.getWeekNote = dateNoteService.getWeekNote; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns month note for given date. If such a note doesn't exist, it is created. | ||||
|      * | ||||
|      * @method | ||||
|      * @param {string} date in YYYY-MM format | ||||
|      * @param {BNote} [rootNote] - specify calendar root note, normally leave empty to use the default calendar | ||||
|      * @returns {BNote|null} | ||||
|      */ | ||||
|     this.getMonthNote = dateNoteService.getMonthNote; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns year note for given year. If such a note doesn't exist, it is created. | ||||
|      * | ||||
|      * @method | ||||
|      * @param {string} year in YYYY format | ||||
|      * @param {BNote} [rootNote] - specify calendar root note, normally leave empty to use the default calendar | ||||
|      * @returns {BNote|null} | ||||
|      */ | ||||
|     this.getYearNote = dateNoteService.getYearNote; | ||||
| 
 | ||||
|     /** | ||||
|      * Sort child notes of a given note. | ||||
|      * | ||||
|      * @method | ||||
|      * @param {string} parentNoteId - this note's child notes will be sorted | ||||
|      * @param {object} [sortConfig] | ||||
|      * @param {string} [sortConfig.sortBy=title] - 'title', 'dateCreated', 'dateModified' or a label name | ||||
|      *                                See {@link https://github.com/zadam/trilium/wiki/Sorting} for details.
 | ||||
|      * @param {boolean} [sortConfig.reverse=false] | ||||
|      * @param {boolean} [sortConfig.foldersFirst=false] | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     this.sortNotes = (parentNoteId, sortConfig = {}) => treeService.sortNotes( | ||||
|         parentNoteId, | ||||
|         sortConfig.sortBy || "title", | ||||
| @ -465,85 +543,15 @@ function BackendScriptApi(currentNote, apiParams) { | ||||
|         !!sortConfig.foldersFirst | ||||
|     ); | ||||
| 
 | ||||
|     /** | ||||
|      * This method finds note by its noteId and prefix and either sets it to the given parentNoteId | ||||
|      * or removes the branch (if parentNoteId is not given). | ||||
|      * | ||||
|      * This method looks similar to toggleNoteInParent() but differs because we're looking up branch by prefix. | ||||
|      * | ||||
|      * @method | ||||
|      * @deprecated this method is pretty confusing and serves specialized purpose only | ||||
|      * @param {string} noteId | ||||
|      * @param {string} prefix | ||||
|      * @param {string|null} parentNoteId | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     this.setNoteToParent = treeService.setNoteToParent; | ||||
| 
 | ||||
|     /** | ||||
|      * This functions wraps code which is supposed to be running in transaction. If transaction already | ||||
|      * exists, then we'll use that transaction. | ||||
|      * | ||||
|      * @method | ||||
|      * @param {function} func | ||||
|      * @returns {any} result of func callback | ||||
|      */ | ||||
|     this.transactional = sql.transactional; | ||||
| 
 | ||||
|     /** | ||||
|      * 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 {string} string to escape | ||||
|      * @returns {string} escaped string | ||||
|      */ | ||||
|     this.escapeHtml = utils.escapeHtml; | ||||
| 
 | ||||
|     /** | ||||
|      * @method | ||||
|      * @param {string} string to unescape | ||||
|      * @returns {string} unescaped string | ||||
|      */ | ||||
|     this.unescapeHtml = utils.unescapeHtml; | ||||
| 
 | ||||
|     /** | ||||
|      * sql | ||||
|      * @type {module:sql} | ||||
|      */ | ||||
|     this.sql = sql; | ||||
| 
 | ||||
|     /** | ||||
|      * @method | ||||
|      * @returns {{syncVersion, appVersion, buildRevision, dbVersion, dataDirectory, buildDate}|*} - object representing basic info about running Trilium version | ||||
|      */ | ||||
|     this.getAppInfo = () => appInfo; | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a new launcher to the launchbar. If the launcher (id) already exists, it will be updated. | ||||
|      * | ||||
|      * @method | ||||
|      * @param {object} opts | ||||
|      * @param {string} opts.id - id of the launcher, only alphanumeric at least 6 characters long | ||||
|      * @param {"note" | "script" | "customWidget"} opts.type - one of | ||||
|      *                          * "note" - activating the launcher will navigate to the target note (specified in targetNoteId param) | ||||
|      *                          * "script" -  activating the launcher will execute the script (specified in scriptNoteId param) | ||||
|      *                          * "customWidget" - the launcher will be rendered with a custom widget (specified in widgetNoteId param) | ||||
|      * @param {string} opts.title | ||||
|      * @param {boolean} [opts.isVisible=false] - if true, will be created in the "Visible launchers", otherwise in "Available launchers" | ||||
|      * @param {string} [opts.icon] - name of the boxicon to be used (e.g. "bx-time") | ||||
|      * @param {string} [opts.keyboardShortcut] - will activate the target note/script upon pressing, e.g. "ctrl+e" | ||||
|      * @param {string} [opts.targetNoteId] - for type "note" | ||||
|      * @param {string} [opts.scriptNoteId] - for type "script" | ||||
|      * @param {string} [opts.widgetNoteId] - for type "customWidget" | ||||
|      * @returns {{note: BNote}} | ||||
|      */ | ||||
|      | ||||
|     this.createOrUpdateLauncher = opts => { | ||||
|         if (!opts.id) { throw new Error("ID is a mandatory parameter for api.createOrUpdateLauncher(opts)"); } | ||||
|         if (!opts.id.match(/[a-z0-9]{6,1000}/i)) { throw new Error(`ID must be an alphanumeric string at least 6 characters long.`); } | ||||
| @ -603,42 +611,27 @@ function BackendScriptApi(currentNote, apiParams) { | ||||
|         return { note: launcherNote }; | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * @method | ||||
|      * @param {string} noteId | ||||
|      * @param {string} format - either 'html' or 'markdown' | ||||
|      * @param {string} zipFilePath | ||||
|      * @returns {Promise<void>} | ||||
|      */ | ||||
|     this.exportSubtreeToZipFile = async (noteId, format, zipFilePath) => await exportService.exportToZipFile(noteId, format, zipFilePath); | ||||
| 
 | ||||
|     /** | ||||
|      * Executes given anonymous function on the frontend(s). | ||||
|      * Internally, this serializes the anonymous function into string and sends it to frontend(s) via WebSocket. | ||||
|      * Note that there can be multiple connected frontend instances (e.g. in different tabs). In such case, all | ||||
|      * instances execute the given function. | ||||
|      * | ||||
|      * @method | ||||
|      * @param {string} script - script to be executed on the frontend | ||||
|      * @param {Array.<?>} params - list of parameters to the anonymous function to be sent to frontend | ||||
|      * @returns {undefined} - no return value is provided. | ||||
|      */ | ||||
|     this.runOnFrontend = async (script, params = []) => { | ||||
|         if (typeof script === "function") { | ||||
|             script = script.toString(); | ||||
|     this.runOnFrontend = async (_script, params = []) => { | ||||
|         let script: string; | ||||
|         if (typeof _script === "string") { | ||||
|             script = _script; | ||||
|         } else { | ||||
|             script = _script.toString(); | ||||
|         } | ||||
| 
 | ||||
|         ws.sendMessageToAllClients({ | ||||
|             type: 'execute-script', | ||||
|             script: script, | ||||
|             params: prepareParams(params), | ||||
|             startNoteId: this.startNote.noteId, | ||||
|             startNoteId: this.startNote?.noteId, | ||||
|             currentNoteId: this.currentNote.noteId, | ||||
|             originEntityName: "notes", // currently there's no other entity on the frontend which can trigger event
 | ||||
|             originEntityId: this.originEntity?.noteId || null | ||||
|             originEntityId: (this.originEntity && "noteId" in this.originEntity && (this.originEntity as BNote)?.noteId) || null | ||||
|         }); | ||||
| 
 | ||||
|         function prepareParams(params) { | ||||
|         function prepareParams(params: any[]) { | ||||
|             if (!params) { | ||||
|                 return params; | ||||
|             } | ||||
| @ -654,35 +647,14 @@ function BackendScriptApi(currentNote, apiParams) { | ||||
|         } | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * Sync process can make data intermittently inconsistent. Scripts which require strong data consistency | ||||
|      * can use this function to wait for a possible sync process to finish and prevent new sync process from starting | ||||
|      * while it is running. | ||||
|      * | ||||
|      * Because this is an async process, the inner callback doesn't have automatic transaction handling, so in case | ||||
|      * you need to make some DB changes, you need to surround your call with api.transactional(...) | ||||
|      * | ||||
|      * @method | ||||
|      * @param {function} callback - function to be executed while sync process is not running | ||||
|      * @returns {Promise} - resolves once the callback is finished (callback is awaited) | ||||
|      */ | ||||
|     this.runOutsideOfSync = syncMutex.doExclusively; | ||||
| 
 | ||||
|     /** | ||||
|      * @method | ||||
|      * @param {string} backupName - If the backupName is e.g. "now", then the backup will be written to "backup-now.db" file | ||||
|      * @returns {Promise} - resolves once the backup is finished | ||||
|      */ | ||||
|     this.backupNow = backupService.backupNow; | ||||
|     | ||||
|     /** | ||||
|      * This object contains "at your risk" and "no BC guarantees" objects for advanced use cases. | ||||
|      * | ||||
|      * @property {Becca} becca - provides access to the backend in-memory object graph, see {@link https://github.com/zadam/trilium/blob/master/src/becca/becca.js}
 | ||||
|      */ | ||||
|     this.__private = { | ||||
|         becca | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| module.exports = BackendScriptApi; | ||||
| export = BackendScriptApi as any as { | ||||
|     new (currentNote: BNote, apiParams: ApiParams): Api | ||||
| }; | ||||
							
								
								
									
										7
									
								
								src/services/backend_script_api_interface.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/services/backend_script_api_interface.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| import AbstractBeccaEntity = require("../becca/entities/abstract_becca_entity"); | ||||
| import BNote = require("../becca/entities/bnote"); | ||||
| 
 | ||||
| export interface ApiParams { | ||||
|     startNote?: BNote; | ||||
|     originEntity?: AbstractBeccaEntity<any>; | ||||
| } | ||||
| @ -191,7 +191,7 @@ function getDayNote(dateStr: string, _rootNote: BNote | null = null): BNote { | ||||
|     return dateNote as unknown as BNote; | ||||
| } | ||||
| 
 | ||||
| function getTodayNote(rootNote = null) { | ||||
| function getTodayNote(rootNote: BNote | null = null) { | ||||
|     return getDayNote(dateUtils.localNowDate(), rootNote); | ||||
| } | ||||
| 
 | ||||
| @ -216,7 +216,7 @@ interface WeekNoteOpts { | ||||
|     startOfTheWeek?: StartOfWeek | ||||
| } | ||||
| 
 | ||||
| function getWeekNote(dateStr: string, options: WeekNoteOpts = {}, rootNote = null) { | ||||
| function getWeekNote(dateStr: string, options: WeekNoteOpts = {}, rootNote: BNote | null = null) { | ||||
|     const startOfTheWeek = options.startOfTheWeek || "monday"; | ||||
| 
 | ||||
|     const dateObj = getStartOfTheWeek(dateUtils.parseLocalDate(dateStr), startOfTheWeek); | ||||
|  | ||||
| @ -1,13 +1,18 @@ | ||||
| const eventService = require('./events'); | ||||
| const scriptService = require('./script.js'); | ||||
| const treeService = require('./tree'); | ||||
| const noteService = require('./notes'); | ||||
| const becca = require('../becca/becca'); | ||||
| const BAttribute = require('../becca/entities/battribute'); | ||||
| const hiddenSubtreeService = require('./hidden_subtree'); | ||||
| const oneTimeTimer = require('./one_time_timer'); | ||||
| import eventService = require('./events'); | ||||
| import scriptService = require('./script'); | ||||
| import treeService = require('./tree'); | ||||
| import noteService = require('./notes'); | ||||
| import becca = require('../becca/becca'); | ||||
| import BAttribute = require('../becca/entities/battribute'); | ||||
| import hiddenSubtreeService = require('./hidden_subtree'); | ||||
| import oneTimeTimer = require('./one_time_timer'); | ||||
| import BNote = require('../becca/entities/bnote'); | ||||
| import AbstractBeccaEntity = require('../becca/entities/abstract_becca_entity'); | ||||
| import { DefinitionObject } from './promoted_attribute_definition_interface'; | ||||
| 
 | ||||
| function runAttachedRelations(note, relationName, originEntity) { | ||||
| type Handler = (definition: DefinitionObject, note: BNote, targetNote: BNote) => void; | ||||
| 
 | ||||
| function runAttachedRelations(note: BNote, relationName: string, originEntity: AbstractBeccaEntity<any>) { | ||||
|     if (!note) { | ||||
|         return; | ||||
|     } | ||||
| @ -16,7 +21,7 @@ function runAttachedRelations(note, relationName, originEntity) { | ||||
|     const notesToRun = new Set( | ||||
|         note.getRelations(relationName) | ||||
|             .map(relation => relation.getTargetNote()) | ||||
|             .filter(note => !!note) | ||||
|             .filter(note => !!note) as BNote[] | ||||
|     ); | ||||
| 
 | ||||
|     for (const noteToRun of notesToRun) { | ||||
| @ -42,7 +47,7 @@ eventService.subscribe(eventService.NOTE_TITLE_CHANGED, note => { | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| eventService.subscribe([ eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED ], ({ entityName, entity }) => { | ||||
| eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED], ({ entityName, entity }) => { | ||||
|     if (entityName === 'attributes') { | ||||
|         runAttachedRelations(entity.getNote(), 'runOnAttributeChange', entity); | ||||
| 
 | ||||
| @ -58,7 +63,7 @@ eventService.subscribe([ eventService.ENTITY_CHANGED, eventService.ENTITY_DELETE | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| eventService.subscribe(eventService.ENTITY_CHANGED, ({entityName, entity}) => { | ||||
| eventService.subscribe(eventService.ENTITY_CHANGED, ({ entityName, entity }) => { | ||||
|     if (entityName === 'branches') { | ||||
|         const parentNote = becca.getNote(entity.parentNoteId); | ||||
| 
 | ||||
| @ -74,7 +79,7 @@ eventService.subscribe(eventService.ENTITY_CHANGED, ({entityName, entity}) => { | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| eventService.subscribe(eventService.NOTE_CONTENT_CHANGE, ({entity}) => { | ||||
| eventService.subscribe(eventService.NOTE_CONTENT_CHANGE, ({ entity }) => { | ||||
|     runAttachedRelations(entity, 'runOnNoteContentChange', entity); | ||||
| }); | ||||
| 
 | ||||
| @ -84,6 +89,9 @@ eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) => | ||||
| 
 | ||||
|         if (entity.type === 'relation' && entity.name === 'template') { | ||||
|             const note = becca.getNote(entity.noteId); | ||||
|             if (!note) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             const templateNote = becca.getNote(entity.value); | ||||
| 
 | ||||
| @ -94,6 +102,7 @@ eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) => | ||||
|             const content = note.getContent(); | ||||
| 
 | ||||
|             if (["text", "code"].includes(note.type) | ||||
|                 && typeof content === "string" | ||||
|                 // if the note has already content we're not going to overwrite it with template's one
 | ||||
|                 && (!content || content.trim().length === 0) | ||||
|                 && templateNote.hasStringContent()) { | ||||
| @ -138,7 +147,7 @@ eventService.subscribe(eventService.CHILD_NOTE_CREATED, ({ parentNote, childNote | ||||
|     runAttachedRelations(parentNote, 'runOnChildNoteCreation', childNote); | ||||
| }); | ||||
| 
 | ||||
| function processInverseRelations(entityName, entity, handler) { | ||||
| function processInverseRelations(entityName: string, entity: BAttribute, handler: Handler) { | ||||
|     if (entityName === 'attributes' && entity.type === 'relation') { | ||||
|         const note = entity.getNote(); | ||||
|         const relDefinitions = note.getLabels(`relation:${entity.name}`); | ||||
| @ -149,13 +158,15 @@ function processInverseRelations(entityName, entity, handler) { | ||||
|             if (definition.inverseRelation && definition.inverseRelation.trim()) { | ||||
|                 const targetNote = entity.getTargetNote(); | ||||
| 
 | ||||
|                 if (targetNote) { | ||||
|                     handler(definition, note, targetNote); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| function handleSortedAttribute(entity) { | ||||
| function handleSortedAttribute(entity: BAttribute) { | ||||
|     treeService.sortNotesIfNeeded(entity.noteId); | ||||
| 
 | ||||
|     if (entity.isInheritable) { | ||||
| @ -169,7 +180,7 @@ function handleSortedAttribute(entity) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| function handleMaybeSortingLabel(entity) { | ||||
| function handleMaybeSortingLabel(entity: BAttribute) { | ||||
|     // check if this label is used for sorting, if yes force re-sort
 | ||||
|     const note = becca.notes[entity.noteId]; | ||||
| 
 | ||||
| @ -203,7 +214,7 @@ eventService.subscribe(eventService.ENTITY_CHANGED, ({ entityName, entity }) => | ||||
|             new BAttribute({ | ||||
|                 noteId: targetNote.noteId, | ||||
|                 type: 'relation', | ||||
|                 name: definition.inverseRelation, | ||||
|                 name: definition.inverseRelation || "", | ||||
|                 value: note.noteId, | ||||
|                 isInheritable: entity.isInheritable | ||||
|             }).save(); | ||||
| @ -215,7 +226,7 @@ eventService.subscribe(eventService.ENTITY_CHANGED, ({ entityName, entity }) => | ||||
| }); | ||||
| 
 | ||||
| eventService.subscribe(eventService.ENTITY_DELETED, ({ entityName, entity }) => { | ||||
|     processInverseRelations(entityName, entity, (definition, note, targetNote) => { | ||||
|     processInverseRelations(entityName, entity, (definition: DefinitionObject, note: BNote, targetNote: BNote) => { | ||||
|         // if one inverse attribute is deleted, then the other should be deleted as well
 | ||||
|         const relations = targetNote.getOwnedRelations(definition.inverseRelation); | ||||
| 
 | ||||
| @ -238,6 +249,6 @@ eventService.subscribe(eventService.ENTITY_DELETED, ({ entityName, entity }) => | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| module.exports = { | ||||
| export = { | ||||
|     runAttachedRelations | ||||
| }; | ||||
| @ -239,8 +239,8 @@ async function importZip(taskContext: TaskContext, fileBuffer: Buffer, importRoo | ||||
|             noteId: noteId, | ||||
|             type: resolveNoteType(noteMeta?.type), | ||||
|             mime: noteMeta ? noteMeta.mime : 'text/html', | ||||
|             prefix: noteMeta ? noteMeta.prefix : '', | ||||
|             isExpanded: noteMeta ? noteMeta.isExpanded : false, | ||||
|             prefix: noteMeta?.prefix || '', | ||||
|             isExpanded: !!noteMeta?.isExpanded, | ||||
|             notePosition: (noteMeta && firstNote) ? noteMeta.notePosition : undefined, | ||||
|             isProtected: importRootNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), | ||||
|         }); | ||||
| @ -510,8 +510,8 @@ async function importZip(taskContext: TaskContext, fileBuffer: Buffer, importRoo | ||||
|                 noteId, | ||||
|                 type, | ||||
|                 mime, | ||||
|                 prefix: noteMeta ? noteMeta.prefix : '', | ||||
|                 isExpanded: noteMeta ? noteMeta.isExpanded : false, | ||||
|                 prefix: noteMeta?.prefix || '', | ||||
|                 isExpanded: !!noteMeta?.isExpanded, | ||||
|                 // root notePosition should be ignored since it relates to the original document
 | ||||
|                 // now import root should be placed after existing notes into new parent
 | ||||
|                 notePosition: (noteMeta && firstNote) ? noteMeta.notePosition : undefined, | ||||
|  | ||||
| @ -3,10 +3,12 @@ import { NoteType } from "../becca/entities/rows"; | ||||
| export interface NoteParams { | ||||
|     /** optionally can force specific noteId */ | ||||
|     noteId?: string; | ||||
|     branchId?: string; | ||||
|     parentNoteId: string; | ||||
|     templateNoteId?: string; | ||||
|     title: string; | ||||
|     content: string; | ||||
|     /** text, code, file, image, search, book, relationMap, canvas, webView */ | ||||
|     type: NoteType; | ||||
|     /** default value is derived from default mimes for type */ | ||||
|     mime?: string; | ||||
|  | ||||
| @ -25,6 +25,7 @@ import ws = require('./ws'); | ||||
| import html2plaintext = require('html2plaintext'); | ||||
| import { AttachmentRow, AttributeRow, BranchRow, NoteRow, NoteType } from '../becca/entities/rows'; | ||||
| import TaskContext = require('./task_context'); | ||||
| import { NoteParams } from './note-interface'; | ||||
| 
 | ||||
| interface FoundLink { | ||||
|     name: "imageLink" | "internalLink" | "includeNoteLink" | "relationMapLink", | ||||
| @ -152,31 +153,6 @@ function getAndValidateParent(params: GetValidateParams) { | ||||
|     return parentNote; | ||||
| } | ||||
| 
 | ||||
| interface NoteParams { | ||||
|     /** optionally can force specific noteId */ | ||||
|     noteId?: string; | ||||
|     branchId?: string; | ||||
|     parentNoteId: string; | ||||
|     templateNoteId?: string; | ||||
|     title: string; | ||||
|     content: string; | ||||
|     type: NoteType; | ||||
|     /** default value is derived from default mimes for type */ | ||||
|     mime?: string; | ||||
|     /** default is false */ | ||||
|     isProtected?: boolean; | ||||
|     /** default is false */ | ||||
|     isExpanded?: boolean; | ||||
|     /** default is empty string */ | ||||
|     prefix?: string | null; | ||||
|     /** default is the last existing notePosition in a parent + 10 */ | ||||
|     notePosition?: number; | ||||
|     dateCreated?: string; | ||||
|     utcDateCreated?: string; | ||||
|     ignoreForbiddenParents?: boolean; | ||||
|     target?: "into"; | ||||
| } | ||||
| 
 | ||||
| function createNewNote(params: NoteParams): { | ||||
|     note: BNote; | ||||
|     branch: BBranch; | ||||
|  | ||||
							
								
								
									
										8
									
								
								src/services/promoted_attribute_definition_interface.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/services/promoted_attribute_definition_interface.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| export interface DefinitionObject { | ||||
|     isPromoted?: boolean; | ||||
|     labelType?: string; | ||||
|     multiplicity?: string; | ||||
|     numberPrecision?: number; | ||||
|     promotedAlias?: string; | ||||
|     inverseRelation?: string; | ||||
| } | ||||
| @ -1,11 +1,4 @@ | ||||
| interface DefinitionObject { | ||||
|     isPromoted?: boolean; | ||||
|     labelType?: string; | ||||
|     multiplicity?: string; | ||||
|     numberPrecision?: number; | ||||
|     promotedAlias?: string; | ||||
|     inverseRelation?: string; | ||||
| } | ||||
| import { DefinitionObject } from "./promoted_attribute_definition_interface"; | ||||
| 
 | ||||
| function parse(value: string): DefinitionObject { | ||||
|     const tokens = value.split(',').map(t => t.trim()); | ||||
|  | ||||
| @ -1,28 +1,24 @@ | ||||
| const scriptService = require('./script.js'); | ||||
| const cls = require('./cls'); | ||||
| const sqlInit = require('./sql_init'); | ||||
| const config = require('./config'); | ||||
| const log = require('./log'); | ||||
| const attributeService = require('../services/attributes'); | ||||
| const protectedSessionService = require('../services/protected_session'); | ||||
| const hiddenSubtreeService = require('./hidden_subtree'); | ||||
| import scriptService = require('./script'); | ||||
| import cls = require('./cls'); | ||||
| import sqlInit = require('./sql_init'); | ||||
| import config = require('./config'); | ||||
| import log = require('./log'); | ||||
| import attributeService = require('../services/attributes'); | ||||
| import protectedSessionService = require('../services/protected_session'); | ||||
| import hiddenSubtreeService = require('./hidden_subtree'); | ||||
| import BNote = require('../becca/entities/bnote'); | ||||
| 
 | ||||
| /** | ||||
|  * @param {BNote} note | ||||
|  * @return {int[]} | ||||
|  */ | ||||
| function getRunAtHours(note) { | ||||
| function getRunAtHours(note: BNote): number[] { | ||||
|     try { | ||||
|         return note.getLabelValues('runAtHour').map(hour => parseInt(hour)); | ||||
|     } | ||||
|     catch (e) { | ||||
|     } catch (e: any) { | ||||
|         log.error(`Could not parse runAtHour for note ${note.noteId}: ${e.message}`); | ||||
| 
 | ||||
|         return []; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| function runNotesWithLabel(runAttrValue) { | ||||
| function runNotesWithLabel(runAttrValue: string) { | ||||
|     const instanceName = config.General ? config.General.instanceName : null; | ||||
|     const currentHours = new Date().getHours(); | ||||
|     const notes = attributeService.getNotesWithLabel('run', runAttrValue); | ||||
| @ -34,7 +30,7 @@ function runNotesWithLabel(runAttrValue) { | ||||
|         if ((runOnInstances.length === 0 || runOnInstances.includes(instanceName)) | ||||
|             && (runAtHours.length === 0 || runAtHours.includes(currentHours)) | ||||
|         ) { | ||||
|             scriptService.executeNoteNoException(note, {originEntity: note}); | ||||
|             scriptService.executeNoteNoException(note, { originEntity: note }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,9 +1,22 @@ | ||||
| const ScriptContext = require('./script_context.js'); | ||||
| const cls = require('./cls'); | ||||
| const log = require('./log'); | ||||
| const becca = require('../becca/becca'); | ||||
| import ScriptContext = require('./script_context'); | ||||
| import cls = require('./cls'); | ||||
| import log = require('./log'); | ||||
| import becca = require('../becca/becca'); | ||||
| import BNote = require('../becca/entities/bnote'); | ||||
| import { ApiParams } from './backend_script_api_interface'; | ||||
| 
 | ||||
| function executeNote(note, apiParams) { | ||||
| interface Bundle { | ||||
|     note?: BNote; | ||||
|     noteId?: string; | ||||
|     script: string; | ||||
|     html: string; | ||||
|     allNotes?: BNote[]; | ||||
|     allNoteIds?: string[]; | ||||
| } | ||||
| 
 | ||||
| type ScriptParams = any[]; | ||||
| 
 | ||||
| function executeNote(note: BNote, apiParams: ApiParams) { | ||||
|     if (!note.isJavaScript() || note.getScriptEnv() !== 'backend' || !note.isContentAvailable()) { | ||||
|         log.info(`Cannot execute note ${note.noteId} "${note.title}", note must be of type "Code: JS backend"`); | ||||
| 
 | ||||
| @ -11,11 +24,14 @@ function executeNote(note, apiParams) { | ||||
|     } | ||||
| 
 | ||||
|     const bundle = getScriptBundle(note, true, 'backend'); | ||||
|     if (!bundle) { | ||||
|         throw new Error("Unable to determine bundle."); | ||||
|     } | ||||
|      | ||||
|     return executeBundle(bundle, apiParams); | ||||
| } | ||||
| 
 | ||||
| function executeNoteNoException(note, apiParams) { | ||||
| function executeNoteNoException(note: BNote, apiParams: ApiParams) { | ||||
|     try { | ||||
|         executeNote(note, apiParams); | ||||
|     } | ||||
| @ -24,7 +40,7 @@ function executeNoteNoException(note, apiParams) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| function executeBundle(bundle, apiParams = {}) { | ||||
| function executeBundle(bundle: Bundle, apiParams: ApiParams = {}) { | ||||
|     if (!apiParams.startNote) { | ||||
|         // this is the default case, the only exception is when we want to preserve frontend startNote
 | ||||
|         apiParams.startNote = bundle.note; | ||||
| @ -33,19 +49,19 @@ function executeBundle(bundle, apiParams = {}) { | ||||
|     const originalComponentId = cls.get('componentId'); | ||||
| 
 | ||||
|     cls.set('componentId', 'script'); | ||||
|     cls.set('bundleNoteId', bundle.note.noteId); | ||||
|     cls.set('bundleNoteId', bundle.note?.noteId); | ||||
| 
 | ||||
|     // last \r\n is necessary if the script contains line comment on its last line
 | ||||
|     const script = `function() {\r
 | ||||
| ${bundle.script}\r | ||||
| }`;
 | ||||
|     const ctx = new ScriptContext(bundle.allNotes, apiParams); | ||||
|     const ctx = new ScriptContext(bundle.allNotes || [], apiParams); | ||||
| 
 | ||||
|     try { | ||||
|         return execute(ctx, script); | ||||
|     } | ||||
|     catch (e) { | ||||
|         log.error(`Execution of script "${bundle.note.title}" (${bundle.note.noteId}) failed with error: ${e.message}`); | ||||
|     catch (e: any) { | ||||
|         log.error(`Execution of script "${bundle.note?.title}" (${bundle.note?.noteId}) failed with error: ${e.message}`); | ||||
| 
 | ||||
|         throw e; | ||||
|     } | ||||
| @ -61,25 +77,36 @@ ${bundle.script}\r | ||||
|  * This method preserves frontend startNode - that's why we start execution from currentNote and override | ||||
|  * bundle's startNote. | ||||
|  */ | ||||
| function executeScript(script, params, startNoteId, currentNoteId, originEntityName, originEntityId) { | ||||
| function executeScript(script: string, params: ScriptParams, startNoteId: string, currentNoteId: string, originEntityName: string, originEntityId: string) { | ||||
|     const startNote = becca.getNote(startNoteId); | ||||
|     const currentNote = becca.getNote(currentNoteId); | ||||
|     const originEntity = becca.getEntity(originEntityName, originEntityId); | ||||
| 
 | ||||
|     if (!currentNote) { | ||||
|         throw new Error("Cannot find note."); | ||||
|     } | ||||
| 
 | ||||
|     // we're just executing an excerpt of the original frontend script in the backend context, so we must
 | ||||
|     // override normal note's content, and it's mime type / script environment
 | ||||
|     const overrideContent = `return (${script}\r\n)(${getParams(params)})`; | ||||
| 
 | ||||
|     const bundle = getScriptBundle(currentNote, true, 'backend', [], overrideContent); | ||||
|     if (!bundle) { | ||||
|         throw new Error("Unable to determine script bundle."); | ||||
|     } | ||||
| 
 | ||||
|     if (!startNote || !originEntity) { | ||||
|         throw new Error("Missing start note or origin entity."); | ||||
|     } | ||||
| 
 | ||||
|     return executeBundle(bundle, { startNote, originEntity }); | ||||
| } | ||||
| 
 | ||||
| function execute(ctx, script) { | ||||
|     return function() { return eval(`const apiContext = this;\r\n(${script}\r\n)()`); }.call(ctx); | ||||
| function execute(ctx: ScriptContext, script: string) { | ||||
|     return function () { return eval(`const apiContext = this;\r\n(${script}\r\n)()`); }.call(ctx); | ||||
| } | ||||
| 
 | ||||
| function getParams(params) { | ||||
| function getParams(params: ScriptParams) { | ||||
|     if (!params) { | ||||
|         return params; | ||||
|     } | ||||
| @ -94,12 +121,7 @@ function getParams(params) { | ||||
|     }).join(","); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * @param {BNote} note | ||||
|  * @param {string} [script] | ||||
|  * @param {Array} [params] | ||||
|  */ | ||||
| function getScriptBundleForFrontend(note, script, params) { | ||||
| function getScriptBundleForFrontend(note: BNote, script: string, params: ScriptParams) { | ||||
|     let overrideContent = null; | ||||
| 
 | ||||
|     if (script) { | ||||
| @ -113,23 +135,16 @@ function getScriptBundleForFrontend(note, script, params) { | ||||
|     } | ||||
| 
 | ||||
|     // for frontend, we return just noteIds because frontend needs to use its own entity instances
 | ||||
|     bundle.noteId = bundle.note.noteId; | ||||
|     bundle.noteId = bundle.note?.noteId; | ||||
|     delete bundle.note; | ||||
| 
 | ||||
|     bundle.allNoteIds = bundle.allNotes.map(note => note.noteId); | ||||
|     bundle.allNoteIds = bundle.allNotes?.map(note => note.noteId); | ||||
|     delete bundle.allNotes; | ||||
| 
 | ||||
|     return bundle; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * @param {BNote} note | ||||
|  * @param {boolean} [root=true] | ||||
|  * @param {string|null} [scriptEnv] | ||||
|  * @param {string[]} [includedNoteIds] | ||||
|  * @param {string|null} [overrideContent] | ||||
|  */ | ||||
| function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = [], overrideContent = null) { | ||||
| function getScriptBundle(note: BNote, root: boolean = true, scriptEnv: string | null = null, includedNoteIds: string[] = [], overrideContent: string | null = null): Bundle | undefined { | ||||
|     if (!note.isContentAvailable()) { | ||||
|         return; | ||||
|     } | ||||
| @ -146,7 +161,7 @@ function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const bundle = { | ||||
|     const bundle: Bundle = { | ||||
|         note: note, | ||||
|         script: '', | ||||
|         html: '', | ||||
| @ -165,12 +180,16 @@ function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = | ||||
|         const childBundle = getScriptBundle(child, false, scriptEnv, includedNoteIds); | ||||
| 
 | ||||
|         if (childBundle) { | ||||
|             if (childBundle.note) { | ||||
|                 modules.push(childBundle.note); | ||||
|             } | ||||
|             bundle.script += childBundle.script; | ||||
|             bundle.html += childBundle.html; | ||||
|             if (bundle.allNotes && childBundle.allNotes) { | ||||
|                 bundle.allNotes = bundle.allNotes.concat(childBundle.allNotes); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     const moduleNoteIds = modules.map(mod => mod.noteId); | ||||
| 
 | ||||
| @ -196,11 +215,11 @@ return module.exports; | ||||
|     return bundle; | ||||
| } | ||||
| 
 | ||||
| function sanitizeVariableName(str) { | ||||
| function sanitizeVariableName(str: string) { | ||||
|     return str.replace(/[^a-z0-9_]/gim, ""); | ||||
| } | ||||
| 
 | ||||
| module.exports = { | ||||
| export = { | ||||
|     executeNote, | ||||
|     executeNoteNoException, | ||||
|     executeScript, | ||||
| @ -1,22 +0,0 @@ | ||||
| const utils = require('./utils'); | ||||
| const BackendScriptApi = require('./backend_script_api.js'); | ||||
| 
 | ||||
| function ScriptContext(allNotes, apiParams = {}) { | ||||
|     this.modules = {}; | ||||
|     this.notes = utils.toObject(allNotes, note => [note.noteId, note]); | ||||
|     this.apis = utils.toObject(allNotes, note => [note.noteId, new BackendScriptApi(note, apiParams)]); | ||||
|     this.require = moduleNoteIds => { | ||||
|         return moduleName => { | ||||
|             const candidates = allNotes.filter(note => moduleNoteIds.includes(note.noteId)); | ||||
|             const note = candidates.find(c => c.title === moduleName); | ||||
| 
 | ||||
|             if (!note) { | ||||
|                 return require(moduleName); | ||||
|             } | ||||
| 
 | ||||
|             return this.modules[note.noteId].exports; | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| module.exports = ScriptContext; | ||||
							
								
								
									
										37
									
								
								src/services/script_context.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/services/script_context.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| import utils = require('./utils'); | ||||
| import BackendScriptApi = require('./backend_script_api'); | ||||
| import BNote = require('../becca/entities/bnote'); | ||||
| import { ApiParams } from './backend_script_api_interface'; | ||||
| 
 | ||||
| type Module = { | ||||
|     exports: any[]; | ||||
| }; | ||||
| 
 | ||||
| class ScriptContext { | ||||
|     modules: Record<string, Module>; | ||||
|     notes: {}; | ||||
|     apis: {}; | ||||
|     allNotes: BNote[]; | ||||
|      | ||||
|     constructor(allNotes: BNote[], apiParams: ApiParams) { | ||||
|         this.allNotes = allNotes; | ||||
|         this.modules = {}; | ||||
|         this.notes = utils.toObject(allNotes, note => [note.noteId, note]); | ||||
|         this.apis = utils.toObject(allNotes, note => [note.noteId, new BackendScriptApi(note, apiParams)]); | ||||
|     } | ||||
| 
 | ||||
|     require(moduleNoteIds: string[]) { | ||||
|         return (moduleName: string) => { | ||||
|             const candidates = this.allNotes.filter(note => moduleNoteIds.includes(note.noteId)); | ||||
|             const note = candidates.find(c => c.title === moduleName); | ||||
| 
 | ||||
|             if (!note) { | ||||
|                 return require(moduleName); | ||||
|             } | ||||
| 
 | ||||
|             return this.modules[note.noteId].exports; | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| export = ScriptContext; | ||||
| @ -78,7 +78,7 @@ function searchFromRelation(note: BNote, relationName: string) { | ||||
|         return []; | ||||
|     } | ||||
| 
 | ||||
|     const scriptService = require('../../script.js'); // to avoid circular dependency
 | ||||
|     const scriptService = require('../../script'); // TODO: to avoid circular dependency
 | ||||
|     const result = scriptService.executeNote(scriptNote, {originEntity: note}); | ||||
| 
 | ||||
|     if (!Array.isArray(result)) { | ||||
|  | ||||
| @ -42,6 +42,15 @@ interface Message { | ||||
|     message?: string; | ||||
|     reason?: string;     | ||||
|     result?: string; | ||||
| 
 | ||||
|     script?: string; | ||||
|     params?: any[]; | ||||
|     noteId?: string; | ||||
|     messages?: string[]; | ||||
|     startNoteId?: string; | ||||
|     currentNoteId?: string; | ||||
|     originEntityName?: "notes"; | ||||
|     originEntityId?: string | null; | ||||
| } | ||||
| 
 | ||||
| type SessionParser = (req: IncomingMessage, params: {}, cb: () => void) => void; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Elian Doran
						Elian Doran