diff --git a/docs/backend_api/Attribute.html b/docs/backend_api/Attribute.html index 204e91870..378b1db05 100644 --- a/docs/backend_api/Attribute.html +++ b/docs/backend_api/Attribute.html @@ -298,29 +298,6 @@ - - - utcDateCreated - - - - - -string - - - - - - - - - - - - - - utcDateModified @@ -378,7 +355,7 @@
Source:
@@ -493,7 +470,7 @@
Source:
@@ -598,7 +575,7 @@
Source:
@@ -703,7 +680,7 @@
Source:
diff --git a/docs/backend_api/BackendScriptApi.html b/docs/backend_api/BackendScriptApi.html index 86c68b051..2d62dd248 100644 --- a/docs/backend_api/BackendScriptApi.html +++ b/docs/backend_api/BackendScriptApi.html @@ -619,7 +619,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -782,7 +782,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -1068,7 +1068,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -1273,7 +1273,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -1451,7 +1451,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -1652,7 +1652,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -1754,7 +1754,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -2224,7 +2224,7 @@ JSON MIME type. See also createNewNote() for more options.
Source:
@@ -2847,7 +2847,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3202,7 +3202,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3400,7 +3400,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3509,7 +3509,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3618,7 +3618,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3799,7 +3799,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3957,7 +3957,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4110,7 +4110,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4196,7 +4196,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4334,7 +4334,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4391,7 +4391,7 @@ if some action needs to happen on only one specific instance. -

searchForNotes(query, searchContextopt) → {Array.<Note>}

+

searchForNotes(query, searchParamsopt) → {Array.<Note>}

@@ -4469,13 +4469,13 @@ if some action needs to happen on only one specific instance. - searchContext + searchParams -SearchContext +Object @@ -4745,7 +4745,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
Source:
@@ -4878,7 +4878,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
Source:
@@ -5084,7 +5084,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
Source:
@@ -5240,7 +5240,7 @@ exists, then we'll use that transaction.
Source:
diff --git a/docs/backend_api/Note.html b/docs/backend_api/Note.html index 8a89ef597..ed874bc30 100644 --- a/docs/backend_api/Note.html +++ b/docs/backend_api/Note.html @@ -296,29 +296,6 @@ - - - isErased - - - - - -boolean - - - - - - - - - - true if note's content is erased after it has been deleted - - - - dateCreated @@ -445,7 +422,7 @@
Source:
@@ -560,7 +537,7 @@
Source:
@@ -662,7 +639,7 @@
Source:
@@ -840,7 +817,7 @@
Source:
@@ -1040,7 +1017,7 @@
Source:
@@ -1218,7 +1195,7 @@
Source:
@@ -1327,7 +1304,7 @@
Source:
@@ -1429,7 +1406,7 @@
Source:
@@ -1535,7 +1512,7 @@
Source:
@@ -1641,7 +1618,7 @@
Source:
@@ -1743,7 +1720,7 @@
Source:
@@ -1845,7 +1822,7 @@
Source:
@@ -2078,7 +2055,7 @@
Source:
@@ -2276,7 +2253,7 @@
Source:
@@ -2474,7 +2451,7 @@
Source:
@@ -2576,7 +2553,7 @@
Source:
@@ -2727,7 +2704,7 @@
Source:
@@ -2897,7 +2874,7 @@
Source:
@@ -3052,7 +3029,7 @@
Source:
@@ -3167,7 +3144,7 @@
Source:
@@ -3269,7 +3246,7 @@
Source:
@@ -3476,7 +3453,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -3654,7 +3631,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -3812,7 +3789,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -3982,7 +3959,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -4137,7 +4114,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -4295,7 +4272,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -4465,7 +4442,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -4620,7 +4597,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -4778,7 +4755,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -4887,7 +4864,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -5042,7 +5019,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -5212,7 +5189,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -5367,7 +5344,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -5537,7 +5514,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -5688,7 +5665,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -5801,7 +5778,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -5903,7 +5880,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -6009,7 +5986,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -6187,7 +6164,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -6293,7 +6270,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -6448,7 +6425,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -6626,7 +6603,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -6781,7 +6758,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -6936,7 +6913,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -7091,7 +7068,7 @@ Use when inheritance is not needed and/or in batch/performance sensitive operati
Source:
@@ -7202,7 +7179,7 @@ Cache is note instance scoped.
Source:
@@ -7330,7 +7307,7 @@ Cache is note instance scoped.
Source:
@@ -7436,7 +7413,7 @@ Cache is note instance scoped.
Source:
@@ -7542,7 +7519,7 @@ Cache is note instance scoped.
Source:
@@ -7648,7 +7625,7 @@ Cache is note instance scoped.
Source:
@@ -7754,7 +7731,7 @@ Cache is note instance scoped.
Source:
@@ -7860,7 +7837,7 @@ Cache is note instance scoped.
Source:
@@ -8093,7 +8070,7 @@ Cache is note instance scoped.
Source:
@@ -8273,7 +8250,7 @@ Cache is note instance scoped.
Source:
@@ -8453,7 +8430,7 @@ Cache is note instance scoped.
Source:
@@ -8664,7 +8641,7 @@ Cache is note instance scoped.
Source:
@@ -8844,7 +8821,7 @@ Cache is note instance scoped.
Source:
@@ -9024,7 +9001,7 @@ Cache is note instance scoped.
Source:
@@ -9266,7 +9243,7 @@ Cache is note instance scoped.
Source:
@@ -9477,7 +9454,7 @@ Cache is note instance scoped.
Source:
@@ -9688,7 +9665,7 @@ Cache is note instance scoped.
Source:
diff --git a/docs/backend_api/NoteRevision.html b/docs/backend_api/NoteRevision.html index 2194ae2fa..fdaa8cb45 100644 --- a/docs/backend_api/NoteRevision.html +++ b/docs/backend_api/NoteRevision.html @@ -198,29 +198,6 @@ - - - - - - - - isErased - - - - - -boolean - - - - - - - - - @@ -398,7 +375,7 @@
Source:
@@ -513,7 +490,7 @@
Source:
@@ -615,7 +592,7 @@
Source:
diff --git a/docs/backend_api/RecentNote.html b/docs/backend_api/RecentNote.html index b4d473056..e3bea9753 100644 --- a/docs/backend_api/RecentNote.html +++ b/docs/backend_api/RecentNote.html @@ -136,30 +136,7 @@ - isDeleted - - - - - -boolean - - - - - - - - - - - - - - - - - utcDateModified + utcDateCreated @@ -214,7 +191,7 @@
Source:
diff --git a/docs/backend_api/entities_attribute.js.html b/docs/backend_api/entities_attribute.js.html index abf4502c9..0e833e283 100644 --- a/docs/backend_api/entities_attribute.js.html +++ b/docs/backend_api/entities_attribute.js.html @@ -46,7 +46,6 @@ const promotedAttributeDefinitionParser = require("../services/promoted_attribut * @property {boolean} isInheritable - immutable * @property {boolean} isDeleted * @property {string|null} deleteId - ID identifying delete transaction - * @property {string} utcDateCreated * @property {string} utcDateModified * * @extends Entity @@ -54,7 +53,7 @@ const promotedAttributeDefinitionParser = require("../services/promoted_attribut class Attribute extends Entity { static get entityName() { return "attributes"; } static get primaryKeyName() { return "attributeId"; } - static get hashedProperties() { return ["attributeId", "noteId", "type", "name", "value", "isInheritable", "isDeleted", "utcDateCreated"]; } + static get hashedProperties() { return ["attributeId", "noteId", "type", "name", "value", "isInheritable", "isDeleted"]; } constructor(row) { super(row); @@ -62,6 +61,10 @@ class Attribute extends Entity { this.isInheritable = !!this.isInheritable; } + isAutoLink() { + return this.type === 'relation' && ['internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink'].includes(this.name); + } + /** * @returns {Note|null} */ @@ -127,15 +130,9 @@ class Attribute extends Entity { this.isDeleted = false; } - if (!this.utcDateCreated) { - this.utcDateCreated = dateUtils.utcNowDateTime(); - } - super.beforeSaving(); - if (this.isChanged) { - this.utcDateModified = dateUtils.utcNowDateTime(); - } + this.utcDateModified = dateUtils.utcNowDateTime(); } createClone(type, name, value, isInheritable) { @@ -147,7 +144,6 @@ class Attribute extends Entity { position: this.position, isInheritable: isInheritable, isDeleted: false, - utcDateCreated: this.utcDateCreated, utcDateModified: this.utcDateModified }); } diff --git a/docs/backend_api/entities_branch.js.html b/docs/backend_api/entities_branch.js.html index 2dca97bcb..c71894b9d 100644 --- a/docs/backend_api/entities_branch.js.html +++ b/docs/backend_api/entities_branch.js.html @@ -66,7 +66,7 @@ class Branch extends Entity { } beforeSaving() { - if (this.notePosition === undefined) { + if (this.notePosition === undefined || this.notePosition === null) { const maxNotePos = sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [this.parentNoteId]); this.notePosition = maxNotePos === null ? 0 : maxNotePos + 10; } @@ -85,9 +85,7 @@ class Branch extends Entity { super.beforeSaving(); - if (this.isChanged) { - this.utcDateModified = dateUtils.utcNowDateTime(); - } + this.utcDateModified = dateUtils.utcNowDateTime(); } createClone(parentNoteId, notePosition) { diff --git a/docs/backend_api/entities_entity.js.html b/docs/backend_api/entities_entity.js.html index d398acf49..78a82ea00 100644 --- a/docs/backend_api/entities_entity.js.html +++ b/docs/backend_api/entities_entity.js.html @@ -43,18 +43,13 @@ class Entity { } } - if ('isDeleted' in this) { + if ('isDeleted' in this && this.constructor.entityName !== 'recent_notes') { this.isDeleted = !!this.isDeleted; } } beforeSaving() { this.generateIdIfNecessary(); - - const origHash = this.hash; - - this.hash = this.generateHash(); - this.isChanged = origHash !== this.hash; } generateIdIfNecessary() { @@ -73,6 +68,10 @@ class Entity { return utils.hash(contentToHash).substr(0, 10); } + getUtcDateChanged() { + return this.utcDateModified; + } + get repository() { if (!repo) { repo = require('../services/repository'); diff --git a/docs/backend_api/entities_note.js.html b/docs/backend_api/entities_note.js.html index bfd2d1246..778234c83 100644 --- a/docs/backend_api/entities_note.js.html +++ b/docs/backend_api/entities_note.js.html @@ -49,7 +49,6 @@ const RELATION = 'relation'; * @property {boolean} isProtected - true if note is protected * @property {boolean} isDeleted - true if note is deleted * @property {string|null} deleteId - ID identifying delete transaction - * @property {boolean} isErased - true if note's content is erased after it has been deleted * @property {string} dateCreated - local date time (with offset) * @property {string} dateModified - local date time (with offset) * @property {string} utcDateCreated @@ -98,9 +97,9 @@ class Note extends Entity { /** @returns {*} */ getContent(silentNotFoundError = false) { if (this.content === undefined) { - const res = sql.getRow(`SELECT content, hash FROM note_contents WHERE noteId = ?`, [this.noteId]); + const row = sql.getRow(`SELECT content FROM note_contents WHERE noteId = ?`, [this.noteId]); - if (!res) { + if (!row) { if (silentNotFoundError) { return undefined; } @@ -109,7 +108,7 @@ class Note extends Entity { } } - this.content = res.content; + this.content = row.content; if (this.isProtected) { if (this.isContentAvailable) { @@ -171,8 +170,7 @@ class Note extends Entity { noteId: this.noteId, content: content, dateModified: dateUtils.localNowDateTime(), - utcDateModified: dateUtils.utcNowDateTime(), - hash: utils.hash(this.noteId + "|" + content.toString()) + utcDateModified: dateUtils.utcNowDateTime() }; if (this.isProtected) { @@ -186,7 +184,15 @@ class Note extends Entity { sql.upsert("note_contents", "noteId", pojo); - entityChangesService.addNoteContentEntityChange(this.noteId); + const hash = utils.hash(this.noteId + "|" + content.toString()); + + entityChangesService.addEntityChange({ + entityName: 'note_contents', + entityId: this.noteId, + hash: hash, + isErased: false, + utcDateChanged: this.getUtcDateChanged() + }, null); } setJsonContent(content) { @@ -834,7 +840,7 @@ class Note extends Entity { * @returns {boolean} - true if note has children */ hasChildren() { - return (this.getChildNotes()).length > 0; + return this.getChildNotes().length > 0; } /** @@ -932,10 +938,8 @@ class Note extends Entity { super.beforeSaving(); - if (this.isChanged) { - this.dateModified = dateUtils.localNowDateTime(); - this.utcDateModified = dateUtils.utcNowDateTime(); - } + this.dateModified = dateUtils.localNowDateTime(); + this.utcDateModified = dateUtils.utcNowDateTime(); } // cannot be static! diff --git a/docs/backend_api/entities_note_revision.js.html b/docs/backend_api/entities_note_revision.js.html index e1f1a4214..371bc3c06 100644 --- a/docs/backend_api/entities_note_revision.js.html +++ b/docs/backend_api/entities_note_revision.js.html @@ -43,7 +43,6 @@ const entityChangesService = require('../services/entity_changes.js'); * @property {string} type * @property {string} mime * @property {string} title - * @property {boolean} isErased * @property {boolean} isProtected * @property {string} dateLastEdited * @property {string} dateCreated @@ -56,12 +55,11 @@ const entityChangesService = require('../services/entity_changes.js'); class NoteRevision extends Entity { static get entityName() { return "note_revisions"; } static get primaryKeyName() { return "noteRevisionId"; } - static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "isErased", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified"]; } + static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified"]; } constructor(row) { super(row); - this.isErased = !!this.isErased; this.isProtected = !!this.isProtected; if (this.isProtected) { @@ -95,7 +93,7 @@ class NoteRevision extends Entity { /** @returns {*} */ getContent(silentNotFoundError = false) { if (this.content === undefined) { - const res = sql.getRow(`SELECT content, hash FROM note_revision_contents WHERE noteRevisionId = ?`, [this.noteRevisionId]); + const res = sql.getRow(`SELECT content FROM note_revision_contents WHERE noteRevisionId = ?`, [this.noteRevisionId]); if (!res) { if (silentNotFoundError) { @@ -107,7 +105,6 @@ class NoteRevision extends Entity { } this.content = res.content; - if (this.isProtected) { if (protectedSessionService.isProtectedSessionAvailable()) { this.content = protectedSessionService.decrypt(this.content); @@ -134,8 +131,7 @@ class NoteRevision extends Entity { const pojo = { noteRevisionId: this.noteRevisionId, content: content, - utcDateModified: dateUtils.utcNowDateTime(), - hash: utils.hash(this.noteRevisionId + "|" + content) + utcDateModified: dateUtils.utcNowDateTime() }; if (this.isProtected) { @@ -149,15 +145,21 @@ class NoteRevision extends Entity { sql.upsert("note_revision_contents", "noteRevisionId", pojo); - entityChangesService.addNoteRevisionContentEntityChange(this.noteRevisionId); + const hash = utils.hash(this.noteRevisionId + "|" + content); + + entityChangesService.addEntityChange({ + entityName: 'note_revision_contents', + entityId: this.noteRevisionId, + hash: hash, + isErased: false, + utcDateChanged: this.getUtcDateChanged() + }, null); } beforeSaving() { super.beforeSaving(); - if (this.isChanged) { - this.utcDateModified = dateUtils.utcNowDateTime(); - } + this.utcDateModified = dateUtils.utcNowDateTime(); } // cannot be static! diff --git a/docs/backend_api/entities_option.js.html b/docs/backend_api/entities_option.js.html index 255779f2f..c1bbab9ff 100644 --- a/docs/backend_api/entities_option.js.html +++ b/docs/backend_api/entities_option.js.html @@ -60,13 +60,12 @@ class Option extends Entity { super.beforeSaving(); - if (this.isChanged) { - this.utcDateModified = dateUtils.utcNowDateTime(); - } + this.utcDateModified = dateUtils.utcNowDateTime(); } } -module.exports = Option; +module.exports = Option; + diff --git a/docs/backend_api/entities_recent_note.js.html b/docs/backend_api/entities_recent_note.js.html index a24776e2d..7af54b3ae 100644 --- a/docs/backend_api/entities_recent_note.js.html +++ b/docs/backend_api/entities_recent_note.js.html @@ -36,21 +36,15 @@ const dateUtils = require('../services/date_utils'); * * @property {string} noteId * @property {string} notePath - * @property {boolean} isDeleted - * @property {string} utcDateModified + * @property {string} utcDateCreated * * @extends Entity */ class RecentNote extends Entity { static get entityName() { return "recent_notes"; } static get primaryKeyName() { return "noteId"; } - static get hashedProperties() { return ["noteId", "notePath", "utcDateCreated", "isDeleted"]; } beforeSaving() { - if (!this.isDeleted) { - this.isDeleted = false; - } - if (!this.utcDateCreated) { this.utcDateCreated = dateUtils.utcNowDateTime(); } @@ -59,7 +53,8 @@ class RecentNote extends Entity { } } -module.exports = RecentNote; +module.exports = RecentNote; + diff --git a/docs/backend_api/global.html b/docs/backend_api/global.html index a5e851b1e..bfb7edec3 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,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 f399a4d47..98bf9c0fd 100644 --- a/docs/backend_api/services_backend_script_api.js.html +++ b/docs/backend_api/services_backend_script_api.js.html @@ -120,13 +120,15 @@ function BackendScriptApi(currentNote, apiParams) { * * @method * @param {string} query - * @param {SearchContext} [searchContext] + * @param {Object} [searchParams] * @returns {Note[]} */ - this.searchForNotes = (query, searchContext) => { - searchContext = searchContext || new SearchContext(); + this.searchForNotes = (query, searchParams = {}) => { + if (searchParams.includeArchivedNotes === undefined) { + searchParams.includeArchivedNotes = true; + } - const noteIds = searchService.findNotesWithQuery(query, searchContext) + const noteIds = searchService.findNotesWithQuery(query, new SearchContext(searchParams)) .map(sr => sr.noteId); return repository.getNotes(noteIds); diff --git a/docs/frontend_api/Branch.html b/docs/frontend_api/Branch.html index a30e20eed..1670c9962 100644 --- a/docs/frontend_api/Branch.html +++ b/docs/frontend_api/Branch.html @@ -201,7 +201,7 @@ -

isDeleted

+

fromSearchNote

@@ -259,6 +259,64 @@ +

isDeleted

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + +

isExpanded

@@ -607,7 +665,7 @@
Source:
@@ -709,7 +767,7 @@
Source:
@@ -811,7 +869,7 @@
Source:
@@ -913,7 +971,7 @@
Source:
diff --git a/docs/frontend_api/FrontendScriptApi.html b/docs/frontend_api/FrontendScriptApi.html index 4cc250285..2b0aac448 100644 --- a/docs/frontend_api/FrontendScriptApi.html +++ b/docs/frontend_api/FrontendScriptApi.html @@ -81,7 +81,7 @@
Source:
@@ -223,7 +223,7 @@
Source:
@@ -329,7 +329,7 @@
Source:
@@ -435,7 +435,7 @@
Source:
@@ -545,7 +545,7 @@
Source:
@@ -658,7 +658,7 @@
Source:
@@ -768,7 +768,7 @@
Source:
@@ -874,7 +874,7 @@
Source:
@@ -980,7 +980,7 @@
Source:
@@ -1109,7 +1109,7 @@
Source:
@@ -1264,7 +1264,7 @@
Source:
@@ -1419,7 +1419,7 @@
Source:
@@ -1556,7 +1556,7 @@
Source:
@@ -1712,7 +1712,7 @@
Source:
@@ -1748,7 +1748,7 @@ - + @@ -1825,7 +1825,148 @@ - noteTitle + params + + + + + +object + + + + + + + + + <optional>
+ + + + + + + + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
showTooltip + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + + enable/disable tooltip on the link
showNotePath + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + + show also whole note's path as part of the link
+ + + + + + + + + title= @@ -1851,7 +1992,7 @@ - if not present we'll use note title + custom link tile with note's title as default @@ -1892,7 +2033,7 @@
Source:
@@ -2025,7 +2166,7 @@
Source:
@@ -2131,7 +2272,7 @@
Source:
@@ -2237,7 +2378,7 @@
Source:
@@ -2391,7 +2532,7 @@
Source:
@@ -2528,7 +2669,7 @@
Source:
@@ -2635,7 +2776,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -2790,7 +2931,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -2946,7 +3087,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3147,7 +3288,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -3253,7 +3394,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -3408,7 +3549,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -3462,6 +3603,184 @@ otherwise (by e.g. createNoteLink()) +

openTabWithNote(notePath, activate) → {Promise.<void>}

+ + + + + + +
+ Open a note in a new tab. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
notePath + + +string + + + + (or noteId)
activate + + +boolean + + + + set to true to activate the new tab, false to stay on the current tab
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +Promise.<void> + + +
+
+ + + + + + + + + + + + +

parseDate(str) → {Date}

@@ -3559,7 +3878,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -3667,7 +3986,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -3823,7 +4142,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -3979,7 +4298,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4111,7 +4430,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4197,7 +4516,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4334,7 +4653,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4495,7 +4814,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -4603,7 +4922,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -4741,7 +5060,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -4897,7 +5216,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -4959,7 +5278,7 @@ Internally this serializes the anonymous function into string and sends it to ba
- Hoist note. See https://github.com/zadam/trilium/wiki/Note-hoisting + Hoist note in the current tab. See https://github.com/zadam/trilium/wiki/Note-hoisting
@@ -5052,7 +5371,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -5203,7 +5522,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -5340,7 +5659,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -5477,7 +5796,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -5569,7 +5888,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/NoteShort.html b/docs/frontend_api/NoteShort.html index 6ff10cc88..89045666e 100644 --- a/docs/frontend_api/NoteShort.html +++ b/docs/frontend_api/NoteShort.html @@ -167,7 +167,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -267,7 +267,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -335,7 +335,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -403,7 +403,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -461,7 +461,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -519,7 +519,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -577,7 +577,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -635,7 +635,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -703,7 +703,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -771,7 +771,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -839,7 +839,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -897,7 +897,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -955,7 +955,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1103,7 +1103,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1303,7 +1303,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1481,7 +1481,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1587,7 +1587,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1689,7 +1689,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1791,7 +1791,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1893,7 +1893,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -1995,7 +1995,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -2146,7 +2146,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -2313,7 +2313,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -2468,7 +2468,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -2578,7 +2578,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -2752,7 +2752,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -2952,7 +2952,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -3130,7 +3130,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -3285,7 +3285,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -3452,7 +3452,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -3607,7 +3607,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -3762,7 +3762,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -3929,7 +3929,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -4084,7 +4084,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -4190,7 +4190,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -4292,7 +4292,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -4443,7 +4443,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -4610,7 +4610,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -4765,7 +4765,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -4935,7 +4935,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -5086,7 +5086,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -5196,7 +5196,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -5302,7 +5302,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -5404,7 +5404,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -5578,7 +5578,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -5684,7 +5684,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -5835,7 +5835,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -6013,7 +6013,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -6168,7 +6168,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -6323,7 +6323,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -6478,7 +6478,7 @@ This note's representation is used in note tree and is kept in TreeCache.
Source:
@@ -6589,7 +6589,7 @@ Cache is note instance scoped.
Source:
@@ -6673,7 +6673,7 @@ Cache is note instance scoped.
Source:
diff --git a/docs/frontend_api/entities_attribute.js.html b/docs/frontend_api/entities_attribute.js.html index a3f06a7d3..c25dca612 100644 --- a/docs/frontend_api/entities_attribute.js.html +++ b/docs/frontend_api/entities_attribute.js.html @@ -50,6 +50,8 @@ class Attribute { this.position = row.position; /** @param {boolean} isInheritable */ this.isInheritable = !!row.isInheritable; + /** @param {boolean} */ + this.isDeleted = !!row.isDeleted; } /** @returns {NoteShort} */ @@ -114,6 +116,17 @@ class Attribute { getDefinition() { return promotedAttributeDefinitionParser.parse(this.value); } + + isDefinitionFor(attr) { + return this.type === 'label' && this.name === `${attr.type}:${attr.name}`; + } + + get dto() { + const dto = Object.assign({}, this); + delete dto.treeCache; + + return dto; + } } export default Attribute; diff --git a/docs/frontend_api/entities_branch.js.html b/docs/frontend_api/entities_branch.js.html index fa6795955..1e84430ea 100644 --- a/docs/frontend_api/entities_branch.js.html +++ b/docs/frontend_api/entities_branch.js.html @@ -48,6 +48,8 @@ class Branch { /** @param {boolean} */ this.isExpanded = !!row.isExpanded; /** @param {boolean} */ + this.fromSearchNote = !!row.fromSearchNote; + /** @param {boolean} */ this.isDeleted = !!row.isDeleted; } @@ -76,7 +78,8 @@ class Branch { } } -export default Branch; +export default Branch; + diff --git a/docs/frontend_api/entities_note_short.js.html b/docs/frontend_api/entities_note_short.js.html index 50a6ef531..c81f0b014 100644 --- a/docs/frontend_api/entities_note_short.js.html +++ b/docs/frontend_api/entities_note_short.js.html @@ -27,12 +27,23 @@
import server from '../services/server.js';
-import Attribute from './attribute.js';
 import noteAttributeCache from "../services/note_attribute_cache.js";
+import ws from "../services/ws.js";
+import options from "../services/options.js";
 
 const LABEL = 'label';
 const RELATION = 'relation';
 
+const NOTE_TYPE_ICONS = {
+    "file": "bx bx-file",
+    "image": "bx bx-image",
+    "code": "bx bx-code",
+    "render": "bx bx-extension",
+    "search": "bx bx-file-find",
+    "relation-map": "bx bx-map-alt",
+    "book": "bx bx-book"
+};
+
 /**
  * FIXME: since there's no "full note" anymore we can rename this to Note
  *
@@ -78,7 +89,7 @@ class NoteShort {
         /** @param {string} content-type, e.g. "application/json" */
         this.mime = row.mime;
         /** @param {boolean} */
-        this.isDeleted = row.isDeleted;
+        this.isDeleted = !!row.isDeleted;
     }
 
     addParent(parentNoteId, branchId) {
@@ -93,14 +104,16 @@ class NoteShort {
         this.parentToBranch[parentNoteId] = branchId;
     }
 
-    addChild(childNoteId, branchId) {
-        if (!this.children.includes(childNoteId)) {
+    addChild(childNoteId, branchId, sort = true) {
+        if (!(childNoteId in this.childToBranch)) {
             this.children.push(childNoteId);
         }
 
         this.childToBranch[childNoteId] = branchId;
 
-        this.sortChildren();
+        if (sort) {
+            this.sortChildren();
+        }
     }
 
     sortChildren() {
@@ -282,6 +295,63 @@ class NoteShort {
         return this.getAttributes(LABEL, name);
     }
 
+    getIcon() {
+        const iconClassLabels = this.getLabels('iconClass');
+        const workspaceIconClass = this.getWorkspaceIconClass();
+
+        if (iconClassLabels.length > 0) {
+            return iconClassLabels.map(l => l.value).join(' ');
+        }
+        else if (workspaceIconClass) {
+            return workspaceIconClass;
+        }
+        else if (this.noteId === 'root') {
+            return "bx bx-chevrons-right";
+        }
+        else if (this.type === 'text') {
+            if (this.isFolder()) {
+                return "bx bx-folder";
+            }
+            else {
+                return "bx bx-note";
+            }
+        }
+        else if (this.type === 'code' && this.mime.startsWith('text/x-sql')) {
+            return "bx bx-data";
+        }
+        else {
+            return NOTE_TYPE_ICONS[this.type];
+        }
+    }
+
+    isFolder() {
+        return this.type === 'search'
+            || this.getFilteredChildBranches().length > 0;
+    }
+
+    getFilteredChildBranches() {
+        let childBranches = this.getChildBranches();
+
+        if (!childBranches) {
+            ws.logError(`No children for ${parentNote}. This shouldn't happen.`);
+            return;
+        }
+
+        if (options.is("hideIncludedImages_main")) {
+            const imageLinks = this.getRelations('imageLink');
+
+            // image is already visible in the parent note so no need to display it separately in the book
+            childBranches = childBranches.filter(branch => !imageLinks.find(rel => rel.value === branch.noteId));
+        }
+
+        // we're not checking hideArchivedNotes since that would mean we need to lazy load the child notes
+        // which would seriously slow down everything.
+        // we check this flag only once user chooses to expand the parent. This has the negative consequence that
+        // note may appear as folder but not contain any children when all of them are archived
+
+        return childBranches;
+    }
+
     /**
      * @param {string} [name] - relation name to filter
      * @returns {Attribute[]} all note's relations (attributes with type relation), including inherited ones
@@ -466,19 +536,42 @@ class NoteShort {
         return relations.map(rel => this.treeCache.notes[rel.value]);
     }
 
-    hasAncestor(ancestorNote) {
+    getPromotedDefinitionAttributes() {
+        if (this.hasLabel('hidePromotedAttributes')) {
+            return [];
+        }
+
+        return this.getAttributes()
+            .filter(attr => attr.isDefinition())
+            .filter(attr => {
+                const def = attr.getDefinition();
+
+                return def && def.isPromoted;
+            });
+    }
+
+    hasAncestor(ancestorNote, visitedNoteIds) {
         if (this.noteId === ancestorNote.noteId) {
             return true;
         }
 
+        if (!visitedNoteIds) {
+            visitedNoteIds = new Set();
+        } else if (visitedNoteIds.has(this.noteId)) {
+            // to avoid infinite cycle when template is descendent of the instance
+            return false;
+        }
+
+        visitedNoteIds.add(this.noteId);
+
         for (const templateNote of this.getTemplateNotes()) {
-            if (templateNote.hasAncestor(ancestorNote)) {
+            if (templateNote.hasAncestor(ancestorNote, visitedNoteIds)) {
                 return true;
             }
         }
 
         for (const parentNote of this.getParentNotes()) {
-            if (parentNote.hasAncestor(ancestorNote)) {
+            if (parentNote.hasAncestor(ancestorNote, visitedNoteIds)) {
                 return true;
             }
         }
@@ -539,6 +632,16 @@ class NoteShort {
         const labels = this.getLabels('cssClass');
         return labels.map(l => l.value).join(' ');
     }
+
+    getWorkspaceIconClass() {
+        const labels = this.getLabels('workspaceIconClass');
+        return labels.length > 0 ? labels[0].value : "";
+    }
+
+    getWorkspaceTabBackgroundColor() {
+        const labels = this.getLabels('workspaceTabBackgroundColor');
+        return labels.length > 0 ? labels[0].value : "";
+    }
 }
 
 export default NoteShort;
diff --git a/docs/frontend_api/global.html b/docs/frontend_api/global.html
index 1be03111a..c0139c144 100644
--- a/docs/frontend_api/global.html
+++ b/docs/frontend_api/global.html
@@ -156,7 +156,7 @@
     
     
Source:
@@ -244,7 +244,7 @@
Source:
@@ -333,7 +333,7 @@ separately but should behave uniformly for the user.
Source:
@@ -572,7 +572,7 @@ separately but should behave uniformly for the user.
Source:
diff --git a/docs/frontend_api/services_frontend_script_api.js.html b/docs/frontend_api/services_frontend_script_api.js.html index 4e20f4e31..55df0a765 100644 --- a/docs/frontend_api/services_frontend_script_api.js.html +++ b/docs/frontend_api/services_frontend_script_api.js.html @@ -26,8 +26,7 @@
-
import treeService from './tree.js';
-import server from './server.js';
+            
import server from './server.js';
 import utils from './utils.js';
 import toastService from './toast.js';
 import linkService from './link.js';
@@ -37,7 +36,6 @@ import protectedSessionService from './protected_session.js';
 import dateNotesService from './date_notes.js';
 import CollapsibleWidget from '../widgets/collapsible_widget.js';
 import ws from "./ws.js";
-import hoistedNoteService from "./hoisted_note.js";
 import appContext from "./app_context.js";
 import TabAwareWidget from "../widgets/tab_aware_widget.js";
 import TabCachingWidget from "../widgets/tab_caching_widget.js";
@@ -101,6 +99,20 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
         appContext.triggerEvent('focusAndSelectTitle');
     };
 
+    /**
+     * Open a note in a new tab.
+     *
+     * @param {string} notePath (or noteId)
+     * @param {boolean} activate - set to true to activate the new tab, false to stay on the current tab
+     * @return {Promise<void>}
+     */
+    this.openTabWithNote = async (notePath, activate) => {
+        await ws.waitForMaxKnownEntityChangeId();
+
+        await appContext.tabManager.openTabWithNote(notePath, activate);
+        appContext.triggerEvent('focusAndSelectTitle');
+    };
+
     /**
      * @typedef {Object} ToolbarButtonOptions
      * @property {string} title
@@ -117,7 +129,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
     this.addButtonToToolbar = opts => {
         const buttonId = "toolbar-button-" + opts.title.replace(/\s/g, "-");
 
-        const button = $('<button>')
+        const button = $('<button class="noborder">')
             .addClass("btn btn-sm")
             .on('click', opts.action);
 
@@ -302,7 +314,10 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
      *
      * @method
      * @param {string} notePath (or noteId)
-     * @param {string} [noteTitle] - if not present we'll use note title
+     * @param {object} [params]
+     * @param {boolean} [params.showTooltip=true] - enable/disable tooltip on the link
+     * @param {boolean} [params.showNotePath=false] - show also whole note's path as part of the link
+     * @param {string} [title=] - custom link tile with note's title as default
      */
     this.createNoteLink = linkService.createNoteLink;
 
@@ -404,13 +419,19 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
     this.getYearNote = dateNotesService.getYearNote;
 
     /**
-     * Hoist note. See https://github.com/zadam/trilium/wiki/Note-hoisting
+     * Hoist note in the current tab. See https://github.com/zadam/trilium/wiki/Note-hoisting
      *
      * @method
      * @param {string} noteId - set hoisted note. 'root' will effectively unhoist
      * @return {Promise}
      */
-    this.setHoistedNoteId = hoistedNoteService.setHoistedNoteId;
+    this.setHoistedNoteId = (noteId) => {
+        const activeTabContext = appContext.tabManager.getActiveTabContext();
+
+        if (activeTabContext) {
+            activeTabContext.setHoistedNoteId(noteId);
+        }
+    };
 
     /**
      * @method
diff --git a/docs/frontend_api/widgets_collapsible_widget.js.html b/docs/frontend_api/widgets_collapsible_widget.js.html
index eb09e089e..9b90ba367 100644
--- a/docs/frontend_api/widgets_collapsible_widget.js.html
+++ b/docs/frontend_api/widgets_collapsible_widget.js.html
@@ -31,16 +31,25 @@ import options from "../services/options.js";
 
 const WIDGET_TPL = `
 <div class="card widget">
-    <div class="card-header">
+    <div class="card-header">    
         <div>           
-            <button class="btn btn-sm widget-title" data-toggle="collapse" data-target="#[to be set]">
-                Collapsible Group Item
-            </button>
-            
-            <a class="widget-help external no-arrow bx bx-info-circle"></a>
+            <a class="widget-toggle-button no-arrow" 
+                title="Minimize/maximize widget"
+                data-toggle="collapse" data-target="#[to be set]">
+                
+                <span class="widget-toggle-icon bx"></span>
+                
+                <span class="widget-title">
+                    Collapsible Group Item
+                </span>    
+            </a>
+        
+            <span class="widget-header-actions"></span>
         </div>
         
-        <div class="widget-header-actions"></div>
+        <div>
+            <a class="widget-help external no-arrow bx bx-info-circle"></a>
+        </div>
     </div>
 
     <div id="[to be set]" class="collapse body-wrapper" style="transition: none; ">
@@ -66,13 +75,19 @@ export default class CollapsibleWidget extends TabAwareWidget {
         // not using constructor name because of webpack mangling class names ...
         this.widgetName = this.widgetTitle.replace(/[^[a-zA-Z0-9]/g, "_");
 
-        if (!options.is(this.widgetName + 'Collapsed')) {
+        this.$toggleButton = this.$widget.find('.widget-toggle-button');
+        this.$toggleIcon = this.$widget.find('.widget-toggle-icon');
+
+        const collapsed = options.is(this.widgetName + 'Collapsed');
+        if (!collapsed) {
             this.$bodyWrapper.collapse("show");
         }
 
+        this.updateToggleIcon(collapsed);
+
         // using immediate variants of the event so that the previous collapse is not caught
-        this.$bodyWrapper.on('hide.bs.collapse', () => this.saveCollapsed(true));
-        this.$bodyWrapper.on('show.bs.collapse', () => this.saveCollapsed(false));
+        this.$bodyWrapper.on('hide.bs.collapse', () => this.toggleCollapsed(true));
+        this.$bodyWrapper.on('show.bs.collapse', () => this.toggleCollapsed(false));
 
         this.$body = this.$bodyWrapper.find('.card-body');
 
@@ -94,19 +109,35 @@ export default class CollapsibleWidget extends TabAwareWidget {
         }
 
         this.$headerActions = this.$widget.find('.widget-header-actions');
-        this.$headerActions.append(...this.headerActions);
+        this.$headerActions.append(this.headerActions);
 
         this.initialized = this.doRenderBody();
 
         this.decorateWidget();
     }
 
-    saveCollapsed(collapse) {
+    toggleCollapsed(collapse) {
+        this.updateToggleIcon(collapse);
+
         options.save(this.widgetName + 'Collapsed', collapse.toString());
 
         this.triggerEvent(`widgetCollapsedStateChanged`, {widgetName: this.widgetName, collapse});
     }
 
+    updateToggleIcon(collapse) {
+        if (collapse) {
+            this.$toggleIcon
+                .addClass("bx-chevron-right")
+                .removeClass("bx-chevron-down")
+                .attr("title", "Show");
+        } else {
+            this.$toggleIcon
+                .addClass("bx-chevron-down")
+                .removeClass("bx-chevron-right")
+                .attr("title", "Hide");
+        }
+    }
+
     /**
      * This event is used to synchronize collapsed state of all the tab-cached widgets since they are all rendered
      * separately but should behave uniformly for the user.
diff --git a/src/public/app/services/frontend_script_api.js b/src/public/app/services/frontend_script_api.js
index b3da91cc0..3edee67b1 100644
--- a/src/public/app/services/frontend_script_api.js
+++ b/src/public/app/services/frontend_script_api.js
@@ -71,6 +71,23 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
         appContext.triggerEvent('focusAndSelectTitle');
     };
 
+    /**
+     * Open a note in a new tab.
+     *
+     * @param {string} notePath (or noteId)
+     * @param {boolean} activate - set to true to activate the new tab, false to stay on the current tab
+     * @return {Promise}
+     */
+    this.openTabWithNote = async (notePath, activate) => {
+        await ws.waitForMaxKnownEntityChangeId();
+
+        await appContext.tabManager.openTabWithNote(notePath, activate);
+
+        if (activate) {
+            appContext.triggerEvent('focusAndSelectTitle');
+        }
+    };
+
     /**
      * @typedef {Object} ToolbarButtonOptions
      * @property {string} title