From 8ec2547b4a6fd37df5e62d67a8245cefc8652c45 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 22 Dec 2022 14:57:00 +0100 Subject: [PATCH] backend API to create a launcher --- db/demo.zip | Bin 874635 -> 882795 bytes docs/backend_api/Attribute.html | 6 +- docs/backend_api/BackendScriptApi.html | 217 +++++++-- .../becca_entities_abstract_entity.js.html | 2 +- .../becca_entities_attribute.js.html | 14 +- .../backend_api/becca_entities_branch.js.html | 6 +- docs/backend_api/becca_entities_note.js.html | 20 +- .../becca_entities_note_revision.js.html | 4 +- docs/backend_api/global.html | 423 +++++++++++++++++- docs/backend_api/module-sql.html | 12 +- .../services_backend_script_api.js.html | 84 +++- docs/backend_api/services_sql.js.html | 16 +- docs/frontend_api/FrontendScriptApi.html | 90 ++-- docs/frontend_api/entities_note_short.js.html | 12 +- docs/frontend_api/global.html | 2 +- .../services_frontend_script_api.js.html | 14 +- .../widgets_collapsible_widget.js.html | 2 +- src/becca/entities/attribute.js | 14 +- src/becca/entities/note.js | 2 +- src/public/app/entities/note_short.js | 2 +- .../app/services/frontend_script_api.js | 4 +- src/public/app/services/server.js | 12 +- src/public/app/services/toast.js | 13 + .../widgets/buttons/launcher/note_launcher.js | 2 +- .../floating_buttons/floating_buttons.js | 4 +- .../widgets/ribbon_widgets/script_executor.js | 6 +- src/public/app/widgets/spacer.js | 17 + src/routes/api/special_notes.js | 5 +- src/routes/routes.js | 4 +- src/services/backend_script_api.js | 84 +++- src/services/hidden_subtree.js | 4 +- src/services/special_notes.js | 23 +- 32 files changed, 947 insertions(+), 173 deletions(-) diff --git a/db/demo.zip b/db/demo.zip index aa395f325b299904b596daf724cbcef0e7bc16ea..f9efd3e44338bccf8abf40baea77830cefa41df6 100644 GIT binary patch delta 32648 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 F{{iV&?hpU~ diff --git a/docs/backend_api/Attribute.html b/docs/backend_api/Attribute.html index 434612be7..28e49bde4 100644 --- a/docs/backend_api/Attribute.html +++ b/docs/backend_api/Attribute.html @@ -1176,7 +1176,7 @@ and relation (representing named relationship between source and target note)Source:
@@ -1370,7 +1370,7 @@ and relation (representing named relationship between source and target note)
Source:
@@ -1564,7 +1564,7 @@ and relation (representing named relationship between source and target note)Source:
diff --git a/docs/backend_api/BackendScriptApi.html b/docs/backend_api/BackendScriptApi.html index ea272a14d..c80876aac 100644 --- a/docs/backend_api/BackendScriptApi.html +++ b/docs/backend_api/BackendScriptApi.html @@ -81,7 +81,7 @@
Source:
@@ -185,7 +185,7 @@
Source:
@@ -295,7 +295,7 @@
Source:
@@ -405,7 +405,7 @@
Source:
@@ -515,7 +515,7 @@
Source:
@@ -625,7 +625,7 @@
Source:
@@ -735,7 +735,7 @@
Source:
@@ -845,7 +845,7 @@
Source:
@@ -955,7 +955,7 @@
Source:
@@ -1131,7 +1131,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -1298,7 +1298,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -1584,7 +1584,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -1636,6 +1636,143 @@ JSON MIME type. See also createNewNote() for more options. + + + + + + +

createOrUpdateLauncher(opts)

+ + + + + + +
+ Creates a new launcher to the launchbar. If the launcher (id) already exists, it will be updated. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
opts + + +CreateOrUpdateLauncher + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + @@ -1789,7 +1926,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -1971,7 +2108,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -2172,7 +2309,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -2323,7 +2460,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -2429,7 +2566,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -2587,7 +2724,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -2741,7 +2878,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -2944,7 +3081,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -3145,7 +3282,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -3255,7 +3392,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3456,7 +3593,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3610,7 +3747,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3811,7 +3948,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4012,7 +4149,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4118,7 +4255,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4288,7 +4425,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4522,7 +4659,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4723,7 +4860,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4876,7 +5013,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -5013,7 +5150,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -5121,7 +5258,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -5302,7 +5439,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -5504,7 +5641,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -5713,7 +5850,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
Source:
@@ -5846,7 +5983,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
Source:
@@ -6052,7 +6189,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
Source:
@@ -6208,7 +6345,7 @@ exists, then we'll use that transaction.
Source:
@@ -6363,7 +6500,7 @@ exists, then we'll use that transaction.
Source:
diff --git a/docs/backend_api/becca_entities_abstract_entity.js.html b/docs/backend_api/becca_entities_abstract_entity.js.html index f06a0c9f1..b7b47377a 100644 --- a/docs/backend_api/becca_entities_abstract_entity.js.html +++ b/docs/backend_api/becca_entities_abstract_entity.js.html @@ -59,7 +59,7 @@ class AbstractEntity { let contentToHash = ""; for (const propertyName of this.constructor.hashedProperties) { - contentToHash += "|" + this[propertyName]; + contentToHash += `|${this[propertyName]}`; } if (isDeleted) { diff --git a/docs/backend_api/becca_entities_attribute.js.html b/docs/backend_api/becca_entities_attribute.js.html index fb653b6d2..dbf8a63ba 100644 --- a/docs/backend_api/becca_entities_attribute.js.html +++ b/docs/backend_api/becca_entities_attribute.js.html @@ -117,11 +117,15 @@ class Attribute extends AbstractEntity { validate() { if (!["label", "relation"].includes(this.type)) { - throw new Error(`Invalid attribute type '${this.type}' in attribute '${this.attributeId}'`); + throw new Error(`Invalid attribute type '${this.type}' in attribute '${this.attributeId}' of note '${this.noteId}'`); } if (!this.name?.trim()) { - throw new Error(`Invalid empty name in attribute '${this.attributeId}'`); + throw new Error(`Invalid empty name in attribute '${this.attributeId}' of note '${this.noteId}'`); + } + + if (this.type === 'relation' && !(this.value in this.becca.notes)) { + throw new Error(`Cannot save relation '${this.name}' of note '${this.noteId}' since it target not existing note '${this.value}'.`); } } @@ -204,11 +208,7 @@ class Attribute extends AbstractEntity { beforeSaving() { this.validate(); - if (this.type === 'relation') { - if (!(this.value in this.becca.notes)) { - throw new Error(`Cannot save relation '${this.name}' since it target not existing note '${this.value}'.`); - } - } else if (!this.value) { + if (!this.value) { // null value isn't allowed this.value = ""; } diff --git a/docs/backend_api/becca_entities_branch.js.html b/docs/backend_api/becca_entities_branch.js.html index 5644529d0..36f0cfa45 100644 --- a/docs/backend_api/becca_entities_branch.js.html +++ b/docs/backend_api/becca_entities_branch.js.html @@ -157,7 +157,7 @@ class Branch extends AbstractEntity { * @returns {boolean} */ get isWeak() { - return ['share', 'lbBookmarks'].includes(this.parentNoteId); + return ['_share', 'lbBookmarks'].includes(this.parentNoteId); } /** @@ -213,7 +213,7 @@ class Branch extends AbstractEntity { // first delete children and then parent - this will show up better in recent changes - log.info("Deleting note " + note.noteId); + log.info(`Deleting note ${note.noteId}`); this.becca.notes[note.noteId].isBeingDeleted = true; @@ -239,7 +239,7 @@ class Branch extends AbstractEntity { let maxNotePos = 0; for (const childBranch of this.parentNote.getChildBranches()) { - if (maxNotePos < childBranch.notePosition && childBranch.branchId !== 'hidden') { + if (maxNotePos < childBranch.notePosition && childBranch.branchId !== '_hidden') { maxNotePos = childBranch.notePosition; } } diff --git a/docs/backend_api/becca_entities_note.js.html b/docs/backend_api/becca_entities_note.js.html index bcca49db7..689030bf5 100644 --- a/docs/backend_api/becca_entities_note.js.html +++ b/docs/backend_api/becca_entities_note.js.html @@ -239,7 +239,7 @@ class Note extends AbstractEntity { return undefined; } else { - throw new Error("Cannot find note content for noteId=" + this.noteId); + throw new Error(`Cannot find note content for noteId=${this.noteId}`); } } @@ -332,7 +332,7 @@ class Note extends AbstractEntity { sql.upsert("note_contents", "noteId", pojo); - const hash = utils.hash(this.noteId + "|" + pojo.content.toString()); + const hash = utils.hash(`${this.noteId}|${pojo.content.toString()}`); entityChangesService.addEntityChange({ entityName: 'note_contents', @@ -767,22 +767,22 @@ class Note extends AbstractEntity { */ getFlatText() { if (!this.flatTextCache) { - this.flatTextCache = this.noteId + ' ' + this.type + ' ' + this.mime + ' '; + this.flatTextCache = `${this.noteId} ${this.type} ${this.mime} `; for (const branch of this.parentBranches) { if (branch.prefix) { - this.flatTextCache += branch.prefix + ' '; + this.flatTextCache += `${branch.prefix} `; } } - this.flatTextCache += this.title + ' '; + this.flatTextCache += `${this.title} `; for (const attr of this.getAttributes()) { // it's best to use space as separator since spaces are filtered from the search string by the tokenization into words - this.flatTextCache += (attr.type === 'label' ? '#' : '~') + attr.name; + this.flatTextCache += `${attr.type === 'label' ? '#' : '~'}${attr.name}`; if (attr.value) { - this.flatTextCache += '=' + attr.value; + this.flatTextCache += `=${attr.value}`; } this.flatTextCache += ' '; @@ -932,7 +932,7 @@ class Note extends AbstractEntity { function addSubtreeNotesInner(note, parentNote = null) { // share can be removed after 0.57 since it will be put under hidden - if (note.noteId === 'hidden' || note.noteId === 'share') { + if (note.noteId === '_hidden' || note.noteId === '_share') { return; } @@ -1152,7 +1152,7 @@ class Note extends AbstractEntity { const attributes = this.getOwnedAttributes(); const attr = attributes.find(attr => attr.type === type && attr.name === name); - value = value !== null && value !== undefined ? value.toString() : ""; + value = value?.toString() || ""; if (attr) { if (attr.value !== value) { @@ -1370,7 +1370,7 @@ class Note extends AbstractEntity { } isLaunchBarConfig() { - return this.type === 'launcher' || ['lbRoot', 'lbAvailableLaunchers', 'lbVisibleLaunchers'].includes(this.noteId); + return this.type === 'launcher' || ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(this.noteId); } isOptions() { diff --git a/docs/backend_api/becca_entities_note_revision.js.html b/docs/backend_api/becca_entities_note_revision.js.html index f82037b93..701317705 100644 --- a/docs/backend_api/becca_entities_note_revision.js.html +++ b/docs/backend_api/becca_entities_note_revision.js.html @@ -109,7 +109,7 @@ class NoteRevision extends AbstractEntity { return undefined; } else { - throw new Error("Cannot find note revision content for noteRevisionId=" + this.noteRevisionId); + throw new Error(`Cannot find note revision content for noteRevisionId=${this.noteRevisionId}`); } } @@ -152,7 +152,7 @@ class NoteRevision extends AbstractEntity { sql.upsert("note_revision_contents", "noteRevisionId", pojo); - const hash = utils.hash(this.noteRevisionId + "|" + pojo.content.toString()); + const hash = utils.hash(`${this.noteRevisionId}|${pojo.content.toString()}`); entityChangesService.addEntityChange({ entityName: 'note_revision_contents', diff --git a/docs/backend_api/global.html b/docs/backend_api/global.html index 571777ef8..a50f15612 100644 --- a/docs/backend_api/global.html +++ b/docs/backend_api/global.html @@ -391,7 +391,7 @@
Source:
@@ -579,7 +579,7 @@
Source:
@@ -767,7 +767,7 @@
Source:
@@ -1053,7 +1053,422 @@
Source:
+ + + + + + + + + + + + + + + + +

CreateOrUpdateLauncher

+ + + + + + +
Type:
+
    +
  • + +Object + + +
  • +
+ + + + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
id + + +string + + + + + + + + + + id of the launcher, only alphanumeric at least 6 characters long
type + + +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)
title + + +string + + + + + + + + + +
isVisible + + +boolean + + + + + + <optional>
+ + + +
+ + false + + if true, will be created in the "Visible launchers", otherwise in "Available launchers"
icon + + +string + + + + + + <optional>
+ + + +
+ + name of the boxicon to be used (e.g. "bx-time")
keyboardShortcut + + +string + + + + + + <optional>
+ + + +
+ + will activate the target note/script upon pressing, e.g. "ctrl+e"
targetNoteId + + +string + + + + + + <optional>
+ + + +
+ + for type "note"
scriptNoteId + + +string + + + + + + <optional>
+ + + +
+ + for type "script"
widgetNoteId + + +string + + + + + + <optional>
+ + + +
+ + for type "customWidget"
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
diff --git a/docs/backend_api/module-sql.html b/docs/backend_api/module-sql.html index 5279b0454..58ca58289 100644 --- a/docs/backend_api/module-sql.html +++ b/docs/backend_api/module-sql.html @@ -250,7 +250,7 @@
Source:
@@ -430,7 +430,7 @@
Source:
@@ -632,7 +632,7 @@
Source:
@@ -834,7 +834,7 @@
Source:
@@ -1036,7 +1036,7 @@
Source:
@@ -1238,7 +1238,7 @@
Source:
diff --git a/docs/backend_api/services_backend_script_api.js.html b/docs/backend_api/services_backend_script_api.js.html index b74145bd8..77cbc836e 100644 --- a/docs/backend_api/services_backend_script_api.js.html +++ b/docs/backend_api/services_backend_script_api.js.html @@ -44,6 +44,8 @@ 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.js"); /** * This is the main backend API interface for scripts. It's published in the local "api" object. @@ -478,13 +480,93 @@ function BackendScriptApi(currentNote, apiParams) { * @method * @deprecated - this is now no-op since all the changes should be gracefully handled per widget */ - this.refreshTree = () => {}; + this.refreshTree = () => { + console.warn("api.refreshTree() is a NO-OP and can be removed from your script.") + }; /** * @return {{syncVersion, appVersion, buildRevision, dbVersion, dataDirectory, buildDate}|*} - object representing basic info about running Trilium version */ this.getAppInfo = () => appInfo + /** + * @typedef {Object} CreateOrUpdateLauncher + * @property {string} id - id of the launcher, only alphanumeric at least 6 characters long + * @property {string} 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) + * @property {string} title + * @property {boolean} [isVisible=false] - if true, will be created in the "Visible launchers", otherwise in "Available launchers" + * @property {string} [icon] - name of the boxicon to be used (e.g. "bx-time") + * @property {string} [keyboardShortcut] - will activate the target note/script upon pressing, e.g. "ctrl+e" + * @property {string} [targetNoteId] - for type "note" + * @property {string} [scriptNoteId] - for type "script" + * @property {string} [widgetNoteId] - for type "customWidget" + */ + + /** + * Creates a new launcher to the launchbar. If the launcher (id) already exists, it will be updated. + * + * @param {CreateOrUpdateLauncher} opts + */ + 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.`); } + if (!opts.type) { throw new Error("Launcher Type is a mandatory parameter for api.createOrUpdateLauncher(opts)"); } + if (!["note", "script", "customWidget"].includes(opts.type)) { throw new Error(`Given launcher type '${opts.type}'`); } + if (!opts.title?.trim()) { throw new Error("Title is a mandatory parameter for api.createOrUpdateLauncher(opts)"); } + if (opts.type === 'note' && !opts.targetNoteId) { throw new Error("targetNoteId is mandatory for launchers of type 'note'"); } + if (opts.type === 'script' && !opts.scriptNoteId) { throw new Error("scriptNoteId is mandatory for launchers of type 'script'"); } + if (opts.type === 'customWidget' && !opts.widgetNoteId) { throw new Error("widgetNoteId is mandatory for launchers of type 'customWidget'"); } + + const parentNoteId = !!opts.isVisible ? '_lbVisibleLaunchers' : '_lbAvailableLaunchers'; + const actualId = 'al_' + opts.id; + + const launcherNote = + becca.getNote(opts.id) || + specialNotesService.createLauncher({ + id: actualId, + parentNoteId: parentNoteId, + launcherType: opts.type, + }).note; + + if (launcherNote.title !== opts.title) { + launcherNote.title = opts.title; + launcherNote.save(); + } + + if (launcherNote.getParentBranches().length === 1) { + const branch = launcherNote.getParentBranches()[0]; + + if (branch.parentNoteId !== parentNoteId) { + branchService.moveBranchToNote(branch, parentNoteId); + } + } + + if (opts.type === 'note') { + launcherNote.setRelation('target', opts.targetNoteId); + } else if (opts.type === 'script') { + launcherNote.setRelation('script', opts.scriptNoteId); + } else if (opts.type === 'customWidget') { + launcherNote.setRelation('widget', opts.widgetNoteId); + } else { + throw new Error(`Unrecognized launcher type '${opts.type}'`); + } + + if (opts.keyboardShortcut) { + launcherNote.setLabel('keyboardShortcut', opts.keyboardShortcut); + } else { + launcherNote.removeLabel('keyboardShortcut'); + } + + if (opts.icon) { + launcherNote.setLabel('iconClass', `bx ${opts.icon}`); + } else { + launcherNote.removeLabel('keyboardShortcut'); + } + }; + /** * This object contains "at your risk" and "no BC guarantees" objects for advanced use cases. * diff --git a/docs/backend_api/services_sql.js.html b/docs/backend_api/services_sql.js.html index 56e81262e..91b2d3863 100644 --- a/docs/backend_api/services_sql.js.html +++ b/docs/backend_api/services_sql.js.html @@ -56,14 +56,20 @@ const LOG_ALL_QUERIES = false; function insert(tableName, rec, replace = false) { const keys = Object.keys(rec); if (keys.length === 0) { - log.error("Can't insert empty object into table " + tableName); + log.error(`Can't insert empty object into table ${tableName}`); return; } const columns = keys.join(", "); const questionMarks = keys.map(p => "?").join(", "); - const query = "INSERT " + (replace ? "OR REPLACE" : "") + " INTO " + tableName + "(" + columns + ") VALUES (" + questionMarks + ")"; + const query = `INSERT + ${replace ? "OR REPLACE" : ""} INTO + ${tableName} + ( + ${columns} + ) + VALUES (${questionMarks})`; const res = execute(query, Object.values(rec)); @@ -77,13 +83,13 @@ function replace(tableName, rec) { function upsert(tableName, primaryKey, rec) { const keys = Object.keys(rec); if (keys.length === 0) { - log.error("Can't upsert empty object into table " + tableName); + log.error(`Can't upsert empty object into table ${tableName}`); return; } const columns = keys.join(", "); - const questionMarks = keys.map(colName => "@" + colName).join(", "); + const questionMarks = keys.map(colName => `@${colName}`).join(", "); const updateMarks = keys.map(colName => `${colName} = @${colName}`).join(", "); @@ -300,7 +306,7 @@ function fillParamList(paramIds, truncate = true) { } // doing it manually to avoid this showing up on the sloq query list - const s = stmt(`INSERT INTO param_list VALUES ` + paramIds.map(paramId => `(?)`).join(','), paramIds); + const s = stmt(`INSERT INTO param_list VALUES ${paramIds.map(paramId => `(?)`).join(',')}`, paramIds); s.run(paramIds); } diff --git a/docs/frontend_api/FrontendScriptApi.html b/docs/frontend_api/FrontendScriptApi.html index 4b296420c..83f1f1a75 100644 --- a/docs/frontend_api/FrontendScriptApi.html +++ b/docs/frontend_api/FrontendScriptApi.html @@ -1262,7 +1262,7 @@ -CreateOrUpdateLauncherOptions +AddButtonToToolbarOptions @@ -1301,7 +1301,7 @@ -
Deprecated:
  • you can now create/modify launchers in the
+
Deprecated:
  • you can now create/modify launchers in the top-left Menu -> Configure Launchbar
@@ -1452,7 +1452,7 @@
Source:
@@ -1591,7 +1591,7 @@
Source:
@@ -1799,7 +1799,7 @@
Source:
@@ -2163,7 +2163,7 @@
Source:
@@ -2296,7 +2296,7 @@
Source:
@@ -2406,7 +2406,7 @@
Source:
@@ -2512,7 +2512,7 @@
Source:
@@ -2618,7 +2618,7 @@
Source:
@@ -2728,7 +2728,7 @@
Source:
@@ -2839,7 +2839,7 @@ implementation of actual widget type.
Source:
@@ -2943,7 +2943,7 @@ implementation of actual widget type.
Source:
@@ -3051,7 +3051,7 @@ implementation of actual widget type.
Source:
@@ -3219,7 +3219,7 @@ implementation of actual widget type.
Source:
@@ -3356,7 +3356,7 @@ implementation of actual widget type.
Source:
@@ -3513,7 +3513,7 @@ implementation of actual widget type.
Source:
@@ -3668,7 +3668,7 @@ implementation of actual widget type.
Source:
@@ -3775,7 +3775,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3930,7 +3930,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4086,7 +4086,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4287,7 +4287,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4393,7 +4393,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4548,7 +4548,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4703,7 +4703,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4853,7 +4853,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5342,7 +5342,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5450,7 +5450,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5606,7 +5606,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5762,7 +5762,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5899,7 +5899,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -6053,7 +6053,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -6139,7 +6139,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -6276,7 +6276,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -6437,7 +6437,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -6545,7 +6545,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -6683,7 +6683,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -6839,7 +6839,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -6994,7 +6994,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7145,7 +7145,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7282,7 +7282,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7419,7 +7419,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7579,7 +7579,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7739,7 +7739,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7831,7 +7831,7 @@ Typical use case is when new note has been created, we should wait until it is s
Source:
diff --git a/docs/frontend_api/entities_note_short.js.html b/docs/frontend_api/entities_note_short.js.html index d07ab3507..32fef2a2e 100644 --- a/docs/frontend_api/entities_note_short.js.html +++ b/docs/frontend_api/entities_note_short.js.html @@ -147,7 +147,7 @@ class NoteShort { async getContent() { // we're not caching content since these objects are in froca and as such pretty long lived - const note = await server.get("notes/" + this.noteId); + const note = await server.get(`notes/${this.noteId}`); return note.content; } @@ -372,7 +372,7 @@ class NoteShort { isInHoistedSubTree: path.includes(hoistedNotePath), isArchived: path.find(noteId => froca.notes[noteId].hasLabel('archived')), isSearch: path.find(noteId => froca.notes[noteId].type === 'search'), - isHidden: path.includes("hidden") + isHidden: path.includes('_hidden') })); notePaths.sort((a, b) => { @@ -454,7 +454,7 @@ class NoteShort { else if (this.noteId === 'root') { return "bx bx-chevrons-right"; } - if (this.noteId === 'share') { + if (this.noteId === '_share') { return "bx bx-share-alt"; } else if (this.type === 'text') { @@ -841,7 +841,7 @@ class NoteShort { return await bundleService.getAndExecuteBundle(this.noteId); } else if (env === "backend") { - return await server.post('script/run/' + this.noteId); + const resp = await server.post(`script/run/${this.noteId}`); } else { throw new Error(`Unrecognized env type ${env} for note ${this.noteId}`); @@ -860,7 +860,7 @@ class NoteShort { continue; } - if (parentNote.noteId === 'share' || parentNote.isShared()) { + if (parentNote.noteId === '_share' || parentNote.isShared()) { return true; } } @@ -873,7 +873,7 @@ class NoteShort { } isLaunchBarConfig() { - return this.type === 'launcher' || ['lbRoot', 'lbAvailableLaunchers', 'lbVisibleLaunchers'].includes(this.noteId); + return this.type === 'launcher' || ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(this.noteId); } isOptions() { diff --git a/docs/frontend_api/global.html b/docs/frontend_api/global.html index ebe94b12e..70445bbc2 100644 --- a/docs/frontend_api/global.html +++ b/docs/frontend_api/global.html @@ -194,7 +194,7 @@ -

CreateOrUpdateLauncherOptions

+

AddButtonToToolbarOptions

diff --git a/docs/frontend_api/services_frontend_script_api.js.html b/docs/frontend_api/services_frontend_script_api.js.html index 2a2bf55af..488f338be 100644 --- a/docs/frontend_api/services_frontend_script_api.js.html +++ b/docs/frontend_api/services_frontend_script_api.js.html @@ -134,7 +134,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain }; /** - * @typedef {Object} CreateOrUpdateLauncherOptions + * @typedef {Object} AddButtonToToolbarOptions * @property {string} [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. * @property {string} title @@ -146,10 +146,12 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain /** * Adds a new launcher to the launchbar. If the launcher (id) already exists, it will be updated. * - * @deprecated you can now create/modify launchers in the - * @param {CreateOrUpdateLauncherOptions} opts + * @deprecated you can now create/modify launchers in the top-left Menu -> Configure Launchbar + * @param {AddButtonToToolbarOptions} opts */ 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(); @@ -163,7 +165,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain return params.map(p => { if (typeof p === "function") { - return "!@#Function: " + p.toString(); + return `!@#Function: ${p.toString()}`; } else { return p; @@ -199,7 +201,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain return ret.executionResult; } else { - throw new Error("server error: " + ret.error); + throw new Error(`server error: ${ret.error}`); } }; @@ -590,7 +592,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain this.log = message => { const {noteId} = this.startNote; - message = utils.now() + ": " + message; + message = `${utils.now()}: ${message}`; console.log(`Script ${noteId}: ${message}`); diff --git a/docs/frontend_api/widgets_collapsible_widget.js.html b/docs/frontend_api/widgets_collapsible_widget.js.html index b5871ed12..7df84e660 100644 --- a/docs/frontend_api/widgets_collapsible_widget.js.html +++ b/docs/frontend_api/widgets_collapsible_widget.js.html @@ -48,7 +48,7 @@ export default class CollapsibleWidget extends NoteContextAwareWidget { doRender() { this.$widget = $(WIDGET_TPL); this.contentSized(); - this.$widget.find('[data-target]').attr('data-target', "#" + this.componentId); + this.$widget.find('[data-target]').attr('data-target', `#${this.componentId}`); this.$bodyWrapper = this.$widget.find('.body-wrapper'); this.$bodyWrapper.attr('id', this.componentId); // for toggle to work we need id diff --git a/src/becca/entities/attribute.js b/src/becca/entities/attribute.js index 834631add..045092583 100644 --- a/src/becca/entities/attribute.js +++ b/src/becca/entities/attribute.js @@ -89,11 +89,15 @@ class Attribute extends AbstractEntity { validate() { if (!["label", "relation"].includes(this.type)) { - throw new Error(`Invalid attribute type '${this.type}' in attribute '${this.attributeId}'`); + throw new Error(`Invalid attribute type '${this.type}' in attribute '${this.attributeId}' of note '${this.noteId}'`); } if (!this.name?.trim()) { - throw new Error(`Invalid empty name in attribute '${this.attributeId}'`); + throw new Error(`Invalid empty name in attribute '${this.attributeId}' of note '${this.noteId}'`); + } + + if (this.type === 'relation' && !(this.value in this.becca.notes)) { + throw new Error(`Cannot save relation '${this.name}' of note '${this.noteId}' since it target not existing note '${this.value}'.`); } } @@ -176,11 +180,7 @@ class Attribute extends AbstractEntity { beforeSaving() { this.validate(); - if (this.type === 'relation') { - if (!(this.value in this.becca.notes)) { - throw new Error(`Cannot save relation '${this.name}' since it target not existing note '${this.value}'.`); - } - } else if (!this.value) { + if (!this.value) { // null value isn't allowed this.value = ""; } diff --git a/src/becca/entities/note.js b/src/becca/entities/note.js index c6dabba29..787c6f4f8 100644 --- a/src/becca/entities/note.js +++ b/src/becca/entities/note.js @@ -1124,7 +1124,7 @@ class Note extends AbstractEntity { const attributes = this.getOwnedAttributes(); const attr = attributes.find(attr => attr.type === type && attr.name === name); - value = value !== null && value !== undefined ? value.toString() : ""; + value = value?.toString() || ""; if (attr) { if (attr.value !== value) { diff --git a/src/public/app/entities/note_short.js b/src/public/app/entities/note_short.js index dcd026edb..65ae54d7f 100644 --- a/src/public/app/entities/note_short.js +++ b/src/public/app/entities/note_short.js @@ -813,7 +813,7 @@ class NoteShort { return await bundleService.getAndExecuteBundle(this.noteId); } else if (env === "backend") { - return await server.post(`script/run/${this.noteId}`); + const resp = await server.post(`script/run/${this.noteId}`); } else { throw new Error(`Unrecognized env type ${env} for note ${this.noteId}`); diff --git a/src/public/app/services/frontend_script_api.js b/src/public/app/services/frontend_script_api.js index 9cbceff34..4792810db 100644 --- a/src/public/app/services/frontend_script_api.js +++ b/src/public/app/services/frontend_script_api.js @@ -106,7 +106,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain }; /** - * @typedef {Object} CreateOrUpdateLauncherOptions + * @typedef {Object} AddButtonToToolbarOptions * @property {string} [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. * @property {string} title @@ -119,7 +119,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain * Adds a new launcher to the launchbar. If the launcher (id) already exists, it will be updated. * * @deprecated you can now create/modify launchers in the top-left Menu -> Configure Launchbar - * @param {CreateOrUpdateLauncherOptions} opts + * @param {AddButtonToToolbarOptions} opts */ 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."); diff --git a/src/public/app/services/server.js b/src/public/app/services/server.js index 783fe598b..d23b1e90d 100644 --- a/src/public/app/services/server.js +++ b/src/public/app/services/server.js @@ -105,21 +105,23 @@ async function call(method, url, data, headers = {}) { async function reportError(method, url, statusCode, response) { const toastService = (await import("./toast.js")).default; + let message = response; if (typeof response === 'string') { try { response = JSON.parse(response); + message = response.message; } - catch (e) { throw e;} + catch (e) {} } if ([400, 404].includes(statusCode) && response && typeof response === 'object') { - toastService.showError(response.message); + toastService.showError(message); throw new ValidationError(response); } else { - const message = `Error when calling ${method} ${url}: ${statusCode} - ${response}`; - toastService.showError(message); - toastService.throwError(message); + const title = `${statusCode} ${method} ${url}`; + toastService.showErrorTitleAndMessage(title, message); + toastService.throwError(`${title} - ${message}`); } } diff --git a/src/public/app/services/toast.js b/src/public/app/services/toast.js index dd7d05c48..09c17ce7f 100644 --- a/src/public/app/services/toast.js +++ b/src/public/app/services/toast.js @@ -84,6 +84,18 @@ function showError(message, delay = 10000) { }); } +function showErrorTitleAndMessage(title, message, delay = 10000) { + console.log(utils.now(), "error: ", message); + + toast({ + title: title, + icon: 'alert', + message: message, + autohide: true, + delay + }); +} + function throwError(message) { ws.logError(message); @@ -93,6 +105,7 @@ function throwError(message) { export default { showMessage, showError, + showErrorTitleAndMessage, showAndLogError, throwError, showPersistent, diff --git a/src/public/app/widgets/buttons/launcher/note_launcher.js b/src/public/app/widgets/buttons/launcher/note_launcher.js index 38f3ca2d2..956d03f38 100644 --- a/src/public/app/widgets/buttons/launcher/note_launcher.js +++ b/src/public/app/widgets/buttons/launcher/note_launcher.js @@ -55,7 +55,7 @@ export default class NoteLauncher extends AbstractLauncher { } getTargetNoteId() { - const targetNoteId = this.launcherNote.getRelationValue('targetNote'); + const targetNoteId = this.launcherNote.getRelationValue('target'); if (!targetNoteId) { dialogService.info("This launcher doesn't define target note."); diff --git a/src/public/app/widgets/floating_buttons/floating_buttons.js b/src/public/app/widgets/floating_buttons/floating_buttons.js index 42f8eaef2..1ba2d57cd 100644 --- a/src/public/app/widgets/floating_buttons/floating_buttons.js +++ b/src/public/app/widgets/floating_buttons/floating_buttons.js @@ -20,7 +20,7 @@ const TPL = ` margin-left: 10px; } - .floating-buttons-children > button { + .floating-buttons-children > button, .floating-buttons-children .floating-button { font-size: 150%; padding: 5px 10px 4px 10px; width: 40px; @@ -33,7 +33,7 @@ const TPL = ` justify-content: space-around; } - .floating-buttons-children > button:hover { + .floating-buttons-children > button:hover, .floating-buttons-children .floating-button:hover { text-decoration: none; border-color: var(--button-border-color); } diff --git a/src/public/app/widgets/ribbon_widgets/script_executor.js b/src/public/app/widgets/ribbon_widgets/script_executor.js index d4c49ee3c..892897b3a 100644 --- a/src/public/app/widgets/ribbon_widgets/script_executor.js +++ b/src/public/app/widgets/ribbon_widgets/script_executor.js @@ -8,6 +8,10 @@ const TPL = ` padding: 12px; color: var(--muted-text-color); } + + .execute-description { + margin-bottom: 10px; + }
@@ -52,7 +56,7 @@ export default class ScriptExecutorWidget extends NoteContextAwareWidget { this.$executeButton.text(executeTitle); this.$executeButton.attr('title', executeTitle); - keyboardActionService.updateDisplayedShortcuts(this.$widget);console.trace("ZZZ"); + keyboardActionService.updateDisplayedShortcuts(this.$widget); const executeDescription = note.getLabelValue('executeDescription'); diff --git a/src/public/app/widgets/spacer.js b/src/public/app/widgets/spacer.js index 9281853bb..173958e7a 100644 --- a/src/public/app/widgets/spacer.js +++ b/src/public/app/widgets/spacer.js @@ -1,4 +1,6 @@ import BasicWidget from "./basic_widget.js"; +import contextMenu from "../menus/context_menu.js"; +import appContext from "../components/app_context.js"; const TPL = `
`; @@ -15,5 +17,20 @@ export default class SpacerWidget extends BasicWidget { this.$widget.css("flex-basis", this.baseSize); this.$widget.css("flex-grow", this.growthFactor); this.$widget.css("flex-shrink", 1000); + + this.$widget.on("contextmenu", e => { + this.$widget.tooltip("hide"); + + contextMenu.show({ + x: e.pageX, + y: e.pageY, + items: [ + {title: "Configure Launchbar", command: "showLaunchBarSubtree", uiIcon: "bx bx-sidebar"} + ], + selectMenuItemHandler: ({command}) => { + appContext.triggerCommand(command); + } + }); + }); } } diff --git a/src/routes/api/special_notes.js b/src/routes/api/special_notes.js index 04e41e22b..5ed6e0656 100644 --- a/src/routes/api/special_notes.js +++ b/src/routes/api/special_notes.js @@ -67,7 +67,10 @@ function getHoistedNote() { } function createLauncher(req) { - return specialNotesService.createLauncher(req.params.parentNoteId, req.params.launcherType); + return specialNotesService.createLauncher({ + parentNoteId: req.params.parentNoteId, + launcherType: req.params.launcherType + }); } function resetLauncher(req) { diff --git a/src/routes/routes.js b/src/routes/routes.js index 03d7d1ee5..080cd3b14 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -450,7 +450,9 @@ function handleException(e, method, path, res) { }); } else { res.status(500) - .send(e.message); + .json({ + message: e.message + }); } } diff --git a/src/services/backend_script_api.js b/src/services/backend_script_api.js index 238e604a8..15b885a77 100644 --- a/src/services/backend_script_api.js +++ b/src/services/backend_script_api.js @@ -16,6 +16,8 @@ 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.js"); /** * This is the main backend API interface for scripts. It's published in the local "api" object. @@ -450,13 +452,93 @@ function BackendScriptApi(currentNote, apiParams) { * @method * @deprecated - this is now no-op since all the changes should be gracefully handled per widget */ - this.refreshTree = () => {}; + this.refreshTree = () => { + console.warn("api.refreshTree() is a NO-OP and can be removed from your script.") + }; /** * @return {{syncVersion, appVersion, buildRevision, dbVersion, dataDirectory, buildDate}|*} - object representing basic info about running Trilium version */ this.getAppInfo = () => appInfo + /** + * @typedef {Object} CreateOrUpdateLauncher + * @property {string} id - id of the launcher, only alphanumeric at least 6 characters long + * @property {string} 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) + * @property {string} title + * @property {boolean} [isVisible=false] - if true, will be created in the "Visible launchers", otherwise in "Available launchers" + * @property {string} [icon] - name of the boxicon to be used (e.g. "bx-time") + * @property {string} [keyboardShortcut] - will activate the target note/script upon pressing, e.g. "ctrl+e" + * @property {string} [targetNoteId] - for type "note" + * @property {string} [scriptNoteId] - for type "script" + * @property {string} [widgetNoteId] - for type "customWidget" + */ + + /** + * Creates a new launcher to the launchbar. If the launcher (id) already exists, it will be updated. + * + * @param {CreateOrUpdateLauncher} opts + */ + 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.`); } + if (!opts.type) { throw new Error("Launcher Type is a mandatory parameter for api.createOrUpdateLauncher(opts)"); } + if (!["note", "script", "customWidget"].includes(opts.type)) { throw new Error(`Given launcher type '${opts.type}'`); } + if (!opts.title?.trim()) { throw new Error("Title is a mandatory parameter for api.createOrUpdateLauncher(opts)"); } + if (opts.type === 'note' && !opts.targetNoteId) { throw new Error("targetNoteId is mandatory for launchers of type 'note'"); } + if (opts.type === 'script' && !opts.scriptNoteId) { throw new Error("scriptNoteId is mandatory for launchers of type 'script'"); } + if (opts.type === 'customWidget' && !opts.widgetNoteId) { throw new Error("widgetNoteId is mandatory for launchers of type 'customWidget'"); } + + const parentNoteId = !!opts.isVisible ? '_lbVisibleLaunchers' : '_lbAvailableLaunchers'; + const actualId = 'al_' + opts.id; + + const launcherNote = + becca.getNote(opts.id) || + specialNotesService.createLauncher({ + id: actualId, + parentNoteId: parentNoteId, + launcherType: opts.type, + }).note; + + if (launcherNote.title !== opts.title) { + launcherNote.title = opts.title; + launcherNote.save(); + } + + if (launcherNote.getParentBranches().length === 1) { + const branch = launcherNote.getParentBranches()[0]; + + if (branch.parentNoteId !== parentNoteId) { + branchService.moveBranchToNote(branch, parentNoteId); + } + } + + if (opts.type === 'note') { + launcherNote.setRelation('target', opts.targetNoteId); + } else if (opts.type === 'script') { + launcherNote.setRelation('script', opts.scriptNoteId); + } else if (opts.type === 'customWidget') { + launcherNote.setRelation('widget', opts.widgetNoteId); + } else { + throw new Error(`Unrecognized launcher type '${opts.type}'`); + } + + if (opts.keyboardShortcut) { + launcherNote.setLabel('keyboardShortcut', opts.keyboardShortcut); + } else { + launcherNote.removeLabel('keyboardShortcut'); + } + + if (opts.icon) { + launcherNote.setLabel('iconClass', `bx ${opts.icon}`); + } else { + launcherNote.removeLabel('keyboardShortcut'); + } + }; + /** * This object contains "at your risk" and "no BC guarantees" objects for advanced use cases. * diff --git a/src/services/hidden_subtree.js b/src/services/hidden_subtree.js index b0c397948..eb125b138 100644 --- a/src/services/hidden_subtree.js +++ b/src/services/hidden_subtree.js @@ -96,7 +96,7 @@ const HIDDEN_SUBTREE_DEFINITION = { attributes: [ { type: 'relation', name: 'template', value: LBTPL_BASE }, { type: 'label', name: 'launcherType', value: 'note' }, - { type: 'label', name: 'relation:targetNote', value: 'promoted' }, + { type: 'label', name: 'relation:target', value: 'promoted' }, { type: 'label', name: 'relation:hoistedNote', value: 'promoted' }, { type: 'label', name: 'label:keyboardShortcut', value: 'promoted,text' }, { type: 'label', name: 'docName', value: 'launchbar_note_launcher' } @@ -271,7 +271,7 @@ function checkHiddenSubtreeRecursively(parentNoteId, item) { attrs.push({ type: 'label', name: 'builtinWidget', value: item.builtinWidget }); } else if (item.targetNoteId) { attrs.push({ type: 'relation', name: 'template', value: LBTPL_NOTE_LAUNCHER }); - attrs.push({ type: 'relation', name: 'targetNote', value: item.targetNoteId }); + attrs.push({ type: 'relation', name: 'target', value: item.targetNoteId }); } else { throw new Error(`No action defined for launcher ${JSON.stringify(item)}`); } diff --git a/src/services/special_notes.js b/src/services/special_notes.js index 40d4647eb..dbaf2ce54 100644 --- a/src/services/special_notes.js +++ b/src/services/special_notes.js @@ -144,9 +144,10 @@ function getHoistedNote() { return becca.getNote(cls.getHoistedNoteId()); } -function createScriptLauncher(parentNoteId, forceNoteId = null) { +function createScriptLauncher(parentNoteId, forceId = null) { const note = noteService.createNewNote({ - noteId: forceNoteId, + noteId: forceId, + branchId: forceId, title: "Script Launcher", type: 'launcher', content: '', @@ -157,11 +158,13 @@ function createScriptLauncher(parentNoteId, forceNoteId = null) { return note; } -function createLauncher(parentNoteId, launcherType) { +function createLauncher({parentNoteId, launcherType, id}) { let note; if (launcherType === 'note') { note = noteService.createNewNote({ + noteId: id, + branchId: id, title: "Note Launcher", type: 'launcher', content: '', @@ -170,9 +173,11 @@ function createLauncher(parentNoteId, launcherType) { note.addRelation('template', LBTPL_NOTE_LAUNCHER); } else if (launcherType === 'script') { - note = createScriptLauncher(parentNoteId); + note = createScriptLauncher(parentNoteId, id); } else if (launcherType === 'customWidget') { note = noteService.createNewNote({ + noteId: id, + branchId: id, title: "Widget Launcher", type: 'launcher', content: '', @@ -182,6 +187,8 @@ function createLauncher(parentNoteId, launcherType) { note.addRelation('template', LBTPL_CUSTOM_WIDGET); } else if (launcherType === 'spacer') { note = noteService.createNewNote({ + noteId: id, + branchId: id, title: "Spacer", type: 'launcher', content: '', @@ -233,12 +240,14 @@ function resetLauncher(noteId) { * could mess up the layout - e.g. the sync status being below. */ function createOrUpdateScriptLauncherFromApi(opts) { - const launcherId = opts.id || (`tb${opts.title.replace(/[^[a-z0-9]/gi, "")}`); + if (opts.id && !/^[a-z0-9]+$/i.test(opts.id)) { + throw new Error(`Launcher ID can be alphanumeric only, '${opts.id}' given`); + } + + const launcherId = opts.id || (`tb_${opts.title.toLowerCase().replace(/[^[a-z0-9]/gi, "")}`); if (!opts.title) { throw new Error("Title is mandatory property to create or update a launcher."); - } else if (!/^[a-z0-9]+$/i.test(launcherId)) { - throw new Error(`Launcher ID can be alphanumeric only, '${launcherId}' given`); } const launcherNote = becca.getNote(launcherId)