diff --git a/docs/backend_api/AbstractBeccaEntity.html b/docs/backend_api/AbstractBeccaEntity.html index e13c799bc..328d08f0c 100644 --- a/docs/backend_api/AbstractBeccaEntity.html +++ b/docs/backend_api/AbstractBeccaEntity.html @@ -991,7 +991,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
diff --git a/docs/backend_api/BAttribute.html b/docs/backend_api/BAttribute.html index 8f6af88c6..29eb98f90 100644 --- a/docs/backend_api/BAttribute.html +++ b/docs/backend_api/BAttribute.html @@ -1904,7 +1904,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
diff --git a/docs/backend_api/BBranch.html b/docs/backend_api/BBranch.html index ae6e4e014..f75738ee7 100644 --- a/docs/backend_api/BBranch.html +++ b/docs/backend_api/BBranch.html @@ -1916,7 +1916,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
diff --git a/docs/backend_api/BEtapiToken.html b/docs/backend_api/BEtapiToken.html index 1fab94869..2c6eb8218 100644 --- a/docs/backend_api/BEtapiToken.html +++ b/docs/backend_api/BEtapiToken.html @@ -1461,7 +1461,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
diff --git a/docs/backend_api/BNote.html b/docs/backend_api/BNote.html index d38f35c0f..b1d3e2b60 100644 --- a/docs/backend_api/BNote.html +++ b/docs/backend_api/BNote.html @@ -1318,7 +1318,7 @@ See addLabel, addRelation for more specific methods.
Source:
@@ -1654,7 +1654,7 @@ See addLabel, addRelation for more specific methods.
Source:
@@ -1900,7 +1900,7 @@ returned.
Source:
@@ -2135,7 +2135,7 @@ returned.
Source:
@@ -2335,7 +2335,7 @@ returned.
Source:
@@ -2556,6 +2556,10 @@ returned. +
+ Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) +
+ @@ -2597,7 +2601,7 @@ returned.
Source:
@@ -2703,7 +2707,7 @@ returned.
Source:
@@ -2877,7 +2881,7 @@ returned.
Source:
@@ -3055,7 +3059,7 @@ returned.
Source:
@@ -3316,6 +3320,364 @@ returned. +

getBestNotePath(hoistedNoteIdopt) → {Array.<string>}

+ + + + + + +
+ Returns note path considered to be the "best" +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
hoistedNoteId + + +string + + + + + + <optional>
+ + + + + +
+ + 'root' + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ array of noteIds constituting the particular note path +
+ + + +
+
+ Type +
+
+ +Array.<string> + + +
+
+ + + + + + + + + + + + + +

getBestNotePathString(hoistedNoteIdopt) → {string}

+ + + + + + +
+ Returns note path considered to be the "best" +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
hoistedNoteId + + +string + + + + + + <optional>
+ + + + + +
+ + 'root' + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ serialized note path (e.g. 'root/a1h315/js725h') +
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + + + +

getBranches() → {Array.<BBranch>}

@@ -3878,7 +4240,7 @@ returned.
Source:
@@ -3968,7 +4330,7 @@ returned.
Source:
@@ -4074,7 +4436,7 @@ returned.
Source:
@@ -4332,7 +4694,7 @@ returned.
Source:
@@ -4490,7 +4852,7 @@ returned.
Source:
@@ -4660,7 +5022,7 @@ returned.
Source:
@@ -4827,7 +5189,7 @@ returned.
Source:
@@ -4933,7 +5295,7 @@ returned.
Source:
@@ -5035,7 +5397,7 @@ returned.
Source:
@@ -5215,7 +5577,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -5480,7 +5842,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -5635,7 +5997,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -5793,7 +6155,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -5963,7 +6325,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -6130,7 +6492,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -6285,7 +6647,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -6443,7 +6805,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -6613,7 +6975,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -7061,7 +7423,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -7219,7 +7581,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -7389,7 +7751,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -7604,7 +7966,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -7658,6 +8020,177 @@ This method can be significantly faster than the getAttribute() +

getSortedNotePathRecords(hoistedNoteIdopt) → {Array.<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array.<string>, isHidden: boolean}>}

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
hoistedNoteId + + +string + + + + + + <optional>
+ + + + + +
+ + 'root' + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +Array.<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array.<string>, isHidden: boolean}> + + +
+
+ + + + + + + + + + + + +

getStrongParentBranches() → {Array.<BBranch>}

@@ -7812,7 +8345,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -7914,7 +8447,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -8020,7 +8553,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -8211,7 +8744,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -8962,7 +9495,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -9160,7 +9693,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -9358,7 +9891,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -9556,7 +10089,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -9706,7 +10239,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -9812,7 +10345,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -10282,6 +10815,161 @@ This method can be significantly faster than the getAttribute() +

isLabelTruthy(name) → {boolean}

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + + +string + + + + label name
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ true if label exists (including inherited) and does not have "false" value. +
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + + + +

isRoot() → {boolean}

@@ -10828,7 +11516,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
Source:
@@ -11008,7 +11696,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
Source:
@@ -11188,7 +11876,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
Source:
@@ -11383,7 +12071,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
Source:
@@ -11615,7 +12303,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
Source:
@@ -11795,7 +12483,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
Source:
@@ -11955,7 +12643,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
Source:
@@ -12197,7 +12885,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
Source:
@@ -12408,7 +13096,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
Source:
@@ -12619,7 +13307,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
Source:
@@ -12671,7 +13359,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
diff --git a/docs/backend_api/BNoteRevision.html b/docs/backend_api/BNoteRevision.html index 0951fa14d..3a9ccf0d3 100644 --- a/docs/backend_api/BNoteRevision.html +++ b/docs/backend_api/BNoteRevision.html @@ -2174,7 +2174,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
diff --git a/docs/backend_api/BOption.html b/docs/backend_api/BOption.html index b5990cb92..849051190 100644 --- a/docs/backend_api/BOption.html +++ b/docs/backend_api/BOption.html @@ -267,7 +267,7 @@
Source:
@@ -335,7 +335,7 @@
Source:
@@ -403,7 +403,7 @@
Source:
@@ -471,7 +471,7 @@
Source:
@@ -1319,7 +1319,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
diff --git a/docs/backend_api/BRecentNote.html b/docs/backend_api/BRecentNote.html index b36b38298..cdda77c51 100644 --- a/docs/backend_api/BRecentNote.html +++ b/docs/backend_api/BRecentNote.html @@ -1251,7 +1251,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
diff --git a/docs/backend_api/BackendScriptApi.html b/docs/backend_api/BackendScriptApi.html index 12800f161..ba6ffbc2b 100644 --- a/docs/backend_api/BackendScriptApi.html +++ b/docs/backend_api/BackendScriptApi.html @@ -3254,7 +3254,7 @@ JSON MIME type. See also createNewNote() for more options. -

ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) → {void}

+

ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) → {Object}

@@ -3262,7 +3262,7 @@ JSON MIME type. See also createNewNote() for more options.
- If there's no branch between note and parent note, create one. Otherwise, do nothing. + If there's no branch between note and parent note, create one. Otherwise, do nothing. Returns the new or existing branch.
@@ -3437,7 +3437,7 @@ JSON MIME type. See also createNewNote() for more options.
-void +Object
@@ -7889,7 +7889,7 @@ exists, then we'll use that transaction.
diff --git a/docs/backend_api/becca_entities_abstract_becca_entity.js.html b/docs/backend_api/becca_entities_abstract_becca_entity.js.html index 4036de3f6..b64387b2d 100644 --- a/docs/backend_api/becca_entities_abstract_becca_entity.js.html +++ b/docs/backend_api/becca_entities_abstract_becca_entity.js.html @@ -212,7 +212,7 @@ module.exports = AbstractBeccaEntity;
diff --git a/docs/backend_api/becca_entities_battribute.js.html b/docs/backend_api/becca_entities_battribute.js.html index 83fff96e5..d9715c60b 100644 --- a/docs/backend_api/becca_entities_battribute.js.html +++ b/docs/backend_api/becca_entities_battribute.js.html @@ -124,7 +124,7 @@ class BAttribute extends AbstractBeccaEntity { } 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}'.`); + throw new Error(`Cannot save relation '${this.name}' of note '${this.noteId}' since it targets not existing note '${this.value}'.`); } } @@ -276,7 +276,7 @@ module.exports = BAttribute;
diff --git a/docs/backend_api/becca_entities_bbranch.js.html b/docs/backend_api/becca_entities_bbranch.js.html index b4fa7f567..b2261711d 100644 --- a/docs/backend_api/becca_entities_bbranch.js.html +++ b/docs/backend_api/becca_entities_bbranch.js.html @@ -319,7 +319,7 @@ module.exports = BBranch;
diff --git a/docs/backend_api/becca_entities_betapi_token.js.html b/docs/backend_api/becca_entities_betapi_token.js.html index 92ab19328..329843522 100644 --- a/docs/backend_api/becca_entities_betapi_token.js.html +++ b/docs/backend_api/becca_entities_betapi_token.js.html @@ -120,7 +120,7 @@ module.exports = BEtapiToken;
diff --git a/docs/backend_api/becca_entities_bnote.js.html b/docs/backend_api/becca_entities_bnote.js.html index 97889071a..0c729d838 100644 --- a/docs/backend_api/becca_entities_bnote.js.html +++ b/docs/backend_api/becca_entities_bnote.js.html @@ -125,7 +125,7 @@ class BNote extends AbstractBeccaEntity { * @private */ this.parents = []; /** @type {BNote[]} - * @private*/ + * @private */ this.children = []; /** @type {BAttribute[]} * @private */ @@ -135,11 +135,11 @@ class BNote extends AbstractBeccaEntity { * @private */ this.__attributeCache = null; /** @type {BAttribute[]|null} - * @private*/ + * @private */ this.inheritableAttributeCache = null; /** @type {BAttribute[]} - * @private*/ + * @private */ this.targetRelations = []; this.becca.addNote(this.noteId, this); @@ -560,6 +560,20 @@ class BNote extends AbstractBeccaEntity { */ hasLabel(name, value) { return this.hasAttribute(LABEL, name, value); } + /** + * @param {string} name - label name + * @returns {boolean} true if label exists (including inherited) and does not have "false" value. + */ + isLabelTruthy(name) { + const label = this.getLabel(name); + + if (!label) { + return false; + } + + return label && label.value !== 'false'; + } + /** * @param {string} name - label name * @param {string} [value] - label value @@ -761,6 +775,21 @@ class BNote extends AbstractBeccaEntity { return this.hasAttribute('label', 'archived'); } + areAllNotePathsArchived() { + // there's a slight difference between note being itself archived and all its note paths being archived + // - note is archived when it itself has an archived label or inherits it + // - note does not have or inherit archived label, but each note paths contains a note with (non-inheritable) + // archived label + + const bestNotePathRecord = this.getSortedNotePathRecords()[0]; + + if (!bestNotePathRecord) { + throw new Error(`No note path available for note '${this.noteId}'`); + } + + return bestNotePathRecord.isArchived; + } + hasInheritableArchivedLabel() { for (const attr of this.getAttributes()) { if (attr.name === 'archived' && attr.type === LABEL && attr.isInheritable) { @@ -1164,6 +1193,8 @@ class BNote extends AbstractBeccaEntity { } /** + * Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) + * * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) */ getAllNotePaths() { @@ -1171,18 +1202,73 @@ class BNote extends AbstractBeccaEntity { return [['root']]; } - const notePaths = []; + const parentNotes = this.getParentNotes(); + let notePaths = []; - for (const parentNote of this.getParentNotes()) { - for (const parentPath of parentNote.getAllNotePaths()) { - parentPath.push(this.noteId); - notePaths.push(parentPath); - } + if (parentNotes.length === 1) { // optimization for most common case + notePaths = parentNotes[0].getAllNotePaths(); + } else { + notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths()); + } + + for (const notePath of notePaths) { + notePath.push(this.noteId); } return notePaths; } + /** + * @param {string} [hoistedNoteId='root'] + * @return {Array<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array<string>, isHidden: boolean}>} + */ + getSortedNotePathRecords(hoistedNoteId = 'root') { + const isHoistedRoot = hoistedNoteId === 'root'; + + const notePaths = this.getAllNotePaths().map(path => ({ + notePath: path, + isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId), + isArchived: path.some(noteId => this.becca.notes[noteId].isArchived), + isHidden: path.includes('_hidden') + })); + + notePaths.sort((a, b) => { + if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { + return a.isInHoistedSubTree ? -1 : 1; + } else if (a.isArchived !== b.isArchived) { + return a.isArchived ? 1 : -1; + } else if (a.isHidden !== b.isHidden) { + return a.isHidden ? 1 : -1; + } else { + return a.notePath.length - b.notePath.length; + } + }); + + return notePaths; + } + + /** + * Returns note path considered to be the "best" + * + * @param {string} [hoistedNoteId='root'] + * @return {string[]} array of noteIds constituting the particular note path + */ + getBestNotePath(hoistedNoteId = 'root') { + return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath; + } + + /** + * Returns note path considered to be the "best" + * + * @param {string} [hoistedNoteId='root'] + * @return {string} serialized note path (e.g. 'root/a1h315/js725h') + */ + getBestNotePathString(hoistedNoteId = 'root') { + const notePath = this.getBestNotePath(hoistedNoteId); + + return notePath?.join("/"); + } + /** * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree */ @@ -1196,9 +1282,7 @@ class BNote extends AbstractBeccaEntity { return false; } else if (parentNote.noteId === '_hidden') { continue; - } - - if (!parentNote.isHiddenCompletely()) { + } else if (!parentNote.isHiddenCompletely()) { return false; } } @@ -1392,7 +1476,7 @@ class BNote extends AbstractBeccaEntity { /** * @param parentNoteId - * @returns {{success: boolean, message: string}} + * @returns {{success: boolean, message: string, branchId: string, notePath: string}} */ cloneTo(parentNoteId) { const cloningService = require("../../services/cloning"); @@ -1550,7 +1634,7 @@ module.exports = BNote;
diff --git a/docs/backend_api/becca_entities_bnote_revision.js.html b/docs/backend_api/becca_entities_bnote_revision.js.html index c140f1d10..ea663e51f 100644 --- a/docs/backend_api/becca_entities_bnote_revision.js.html +++ b/docs/backend_api/becca_entities_bnote_revision.js.html @@ -194,12 +194,14 @@ class BNoteRevision extends AbstractBeccaEntity { utcDateLastEdited: this.utcDateLastEdited, utcDateCreated: this.utcDateCreated, utcDateModified: this.utcDateModified, + content: this.content, // used when retrieving full note revision to frontend contentLength: this.contentLength }; } getPojoToSave() { const pojo = this.getPojo(); + delete pojo.content; // not getting persisted delete pojo.contentLength; // not getting persisted if (pojo.isProtected) { @@ -233,7 +235,7 @@ module.exports = BNoteRevision;
diff --git a/docs/backend_api/becca_entities_boption.js.html b/docs/backend_api/becca_entities_boption.js.html index ab2c22f51..0f2856fdc 100644 --- a/docs/backend_api/becca_entities_boption.js.html +++ b/docs/backend_api/becca_entities_boption.js.html @@ -44,6 +44,11 @@ class BOption extends AbstractBeccaEntity { constructor(row) { super(); + this.updateFromRow(row); + this.becca.options[this.name] = this; + } + + updateFromRow(row) { /** @type {string} */ this.name = row.name; /** @type {string} */ @@ -52,8 +57,6 @@ class BOption extends AbstractBeccaEntity { this.isSynced = !!row.isSynced; /** @type {string} */ this.utcDateModified = row.utcDateModified; - - this.becca.options[this.name] = this; } beforeSaving() { @@ -89,7 +92,7 @@ module.exports = BOption;
diff --git a/docs/backend_api/becca_entities_brecent_note.js.html b/docs/backend_api/becca_entities_brecent_note.js.html index 2e2f5ccb1..02f6858fb 100644 --- a/docs/backend_api/becca_entities_brecent_note.js.html +++ b/docs/backend_api/becca_entities_brecent_note.js.html @@ -77,7 +77,7 @@ module.exports = BRecentNote;
diff --git a/docs/backend_api/index.html b/docs/backend_api/index.html index 55d778a7e..f9f7919b5 100644 --- a/docs/backend_api/index.html +++ b/docs/backend_api/index.html @@ -56,7 +56,7 @@
diff --git a/docs/backend_api/module-sql.html b/docs/backend_api/module-sql.html index 6517bc0d4..2605d6530 100644 --- a/docs/backend_api/module-sql.html +++ b/docs/backend_api/module-sql.html @@ -1300,7 +1300,7 @@
diff --git a/docs/backend_api/services_backend_script_api.js.html b/docs/backend_api/services_backend_script_api.js.html index 44f40bed6..bffce4bcd 100644 --- a/docs/backend_api/services_backend_script_api.js.html +++ b/docs/backend_api/services_backend_script_api.js.html @@ -165,13 +165,13 @@ function BackendScriptApi(currentNote, apiParams) { this.getNoteWithLabel = attributeService.getNoteWithLabel; /** - * If there's no branch between note and parent note, create one. Otherwise, do nothing. + * If there's no branch between note and parent note, create one. Otherwise, do nothing. Returns the new or existing branch. * * @method * @param {string} noteId * @param {string} parentNoteId * @param {string} prefix - if branch will be created between note and parent note, set this prefix - * @returns {void} + * @returns {{branch: BBranch|null}} */ this.ensureNoteIsPresentInParent = cloningService.ensureNoteIsPresentInParent; @@ -499,11 +499,11 @@ function BackendScriptApi(currentNote, apiParams) { 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 parentNoteId = opts.isVisible ? '_lbVisibleLaunchers' : '_lbAvailableLaunchers'; const noteId = 'al_' + opts.id; const launcherNote = - becca.getNote(opts.id) || + becca.getNote(noteId) || specialNotesService.createLauncher({ noteId: noteId, parentNoteId: parentNoteId, @@ -542,7 +542,7 @@ function BackendScriptApi(currentNote, apiParams) { if (opts.icon) { launcherNote.setLabel('iconClass', `bx ${opts.icon}`); } else { - launcherNote.removeLabel('keyboardShortcut'); + launcherNote.removeLabel('iconClass'); } return {note: launcherNote}; @@ -584,7 +584,7 @@ module.exports = BackendScriptApi;
diff --git a/docs/backend_api/services_sql.js.html b/docs/backend_api/services_sql.js.html index ec2ac56e6..783bf8301 100644 --- a/docs/backend_api/services_sql.js.html +++ b/docs/backend_api/services_sql.js.html @@ -245,7 +245,7 @@ function wrap(query, func) { // in these cases error should be simply ignored. console.log(e.message); - return null + return null; } throw e; @@ -309,7 +309,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(',')}`); s.run(paramIds); } @@ -413,7 +413,7 @@ module.exports = {
diff --git a/docs/frontend_api/FAttribute.html b/docs/frontend_api/FAttribute.html index 641c3a737..901dc598c 100644 --- a/docs/frontend_api/FAttribute.html +++ b/docs/frontend_api/FAttribute.html @@ -850,7 +850,7 @@ and relation (representing named relationship between source and target note) diff --git a/docs/frontend_api/FBranch.html b/docs/frontend_api/FBranch.html index 284bc72d5..4ec45ea97 100644 --- a/docs/frontend_api/FBranch.html +++ b/docs/frontend_api/FBranch.html @@ -1062,7 +1062,7 @@ parents.
diff --git a/docs/frontend_api/FNote.html b/docs/frontend_api/FNote.html index cff8d1e8f..cf7e9782c 100644 --- a/docs/frontend_api/FNote.html +++ b/docs/frontend_api/FNote.html @@ -977,6 +977,116 @@ +

getAllNotePaths() → {Array.<Array.<string>>}

+ + + + + + +
+ Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ - array of notePaths (each represented by array of noteIds constituting the particular note path) +
+ + + +
+
+ Type +
+
+ +Array.<Array.<string>> + + +
+
+ + + + + + + + + + + + +

getAttribute(type, name) → {FAttribute}

@@ -1097,7 +1207,7 @@
Source:
@@ -1275,7 +1385,7 @@
Source:
@@ -1475,7 +1585,7 @@
Source:
@@ -1533,6 +1643,364 @@ +

getBestNotePath(hoistedNoteIdopt) → {Array.<string>}

+ + + + + + +
+ Returns note path considered to be the "best" +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
hoistedNoteId + + +string + + + + + + <optional>
+ + + + + +
+ + 'root' + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ array of noteIds constituting the particular note path +
+ + + +
+
+ Type +
+
+ +Array.<string> + + +
+
+ + + + + + + + + + + + + +

getBestNotePathString(hoistedNoteIdopt) → {string}

+ + + + + + +
+ Returns note path considered to be the "best" +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
hoistedNoteId + + +string + + + + + + <optional>
+ + + + + +
+ + 'root' + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ serialized note path (e.g. 'root/a1h315/js725h') +
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + + + +

getBranchIds() → {Array.<string>}

@@ -1583,7 +2051,7 @@
Source:
@@ -1687,7 +2155,7 @@
Source:
@@ -1789,7 +2257,7 @@
Source:
@@ -1891,7 +2359,7 @@
Source:
@@ -1993,7 +2461,7 @@
Source:
@@ -2144,7 +2612,7 @@
Source:
@@ -2299,7 +2767,7 @@
Source:
@@ -2466,7 +2934,7 @@
Source:
@@ -2576,7 +3044,7 @@
Source:
@@ -2678,7 +3146,7 @@
Source:
@@ -2852,7 +3320,7 @@
Source:
@@ -3030,7 +3498,7 @@
Source:
@@ -3230,7 +3698,7 @@
Source:
@@ -3385,7 +3853,7 @@
Source:
@@ -3540,7 +4008,7 @@
Source:
@@ -3707,7 +4175,7 @@
Source:
@@ -3862,7 +4330,7 @@
Source:
@@ -4017,7 +4485,7 @@
Source:
@@ -4184,7 +4652,7 @@
Source:
@@ -4290,7 +4758,7 @@
Source:
@@ -4392,7 +4860,7 @@
Source:
@@ -4494,7 +4962,7 @@
Source:
@@ -4596,7 +5064,7 @@
Source:
@@ -4747,7 +5215,7 @@
Source:
@@ -4902,7 +5370,7 @@
Source:
@@ -5072,7 +5540,7 @@
Source:
@@ -5223,7 +5691,7 @@
Source:
@@ -5390,7 +5858,7 @@
Source:
@@ -5496,7 +5964,7 @@
Source:
@@ -5557,6 +6025,177 @@ +

getSortedNotePathRecords(hoistedNoteIdopt) → {Array.<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array.<string>, isHidden: boolean}>}

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
hoistedNoteId + + +string + + + + + + <optional>
+ + + + + +
+ + 'root' + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +Array.<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array.<string>, isHidden: boolean}> + + +
+
+ + + + + + + + + + + + +

(async) getTargetRelationSourceNotes() → {Array.<FNote>}

@@ -5609,7 +6248,7 @@
Source:
@@ -5715,7 +6354,7 @@
Source:
@@ -5889,7 +6528,7 @@
Source:
@@ -5995,7 +6634,7 @@
Source:
@@ -6146,7 +6785,7 @@
Source:
@@ -6324,7 +6963,7 @@
Source:
@@ -6479,7 +7118,7 @@
Source:
@@ -6634,7 +7273,7 @@
Source:
@@ -6789,7 +7428,7 @@
Source:
@@ -6897,7 +7536,7 @@
Source:
@@ -6981,7 +7620,7 @@
Source:
@@ -7075,7 +7714,7 @@
Source:
@@ -7181,7 +7820,7 @@
Source:
@@ -7287,7 +7926,7 @@
Source:
@@ -7336,6 +7975,161 @@ + + + + + +

isLabelTruthy(name) → {boolean}

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + + +string + + + + label name
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ true if label exists (including inherited) and does not have "false" value. +
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + @@ -7357,7 +8151,7 @@
diff --git a/docs/frontend_api/FNoteComplement.html b/docs/frontend_api/FNoteComplement.html index 8c1bb3d94..1cac29fba 100644 --- a/docs/frontend_api/FNoteComplement.html +++ b/docs/frontend_api/FNoteComplement.html @@ -781,7 +781,7 @@
diff --git a/docs/frontend_api/FrontendScriptApi.html b/docs/frontend_api/FrontendScriptApi.html index e866a8473..5e94a32c2 100644 --- a/docs/frontend_api/FrontendScriptApi.html +++ b/docs/frontend_api/FrontendScriptApi.html @@ -342,115 +342,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
- - - - - - - - - - - - - - - - -

CollapsibleWidget

- - - - - - - - - - -
Properties:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDescription
- - -RightPanelWidget - - - -
- - - - -
- - - - - - - - - - - - - - - - -
Deprecated:
  • use api.RightPanelWidget instead
- - - - - - - - - - - -
Source:
-
@@ -556,115 +448,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
- - - - - - - -
- - - - - - - - -

NoteContextCachingWidget

- - - - - - - - - - -
Properties:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDescription
- - -NoteContextAwareWidget - - - -
- - - - -
- - - - - - - - - - - - - - - - -
Deprecated:
  • use NoteContextAwareWidget instead
- - - - - - - - - - - -
Source:
-
@@ -770,223 +554,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
- - - - - - - -
- - - - - - - - -

TabAwareWidget

- - - - - - - - - - -
Properties:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDescription
- - -NoteContextAwareWidget - - - -
- - - - -
- - - - - - - - - - - - - - - - -
Deprecated:
  • use NoteContextAwareWidget instead
- - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - -

TabCachingWidget

- - - - - - - - - - -
Properties:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDescription
- - -NoteContextAwareWidget - - - -
- - - - -
- - - - - - - - - - - - - - - - -
Deprecated:
  • use NoteContextAwareWidget instead
- - - - - - - - - - - -
Source:
-
@@ -1558,7 +1126,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -1713,7 +1281,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -2054,7 +1622,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -2191,7 +1759,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -2399,7 +1967,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -2781,7 +2349,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -2914,7 +2482,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -3024,7 +2592,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -3130,7 +2698,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -3236,7 +2804,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -3346,7 +2914,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -3457,7 +3025,7 @@ implementation of actual widget type.
Source:
@@ -3612,7 +3180,7 @@ implementation of actual widget type.
Source:
@@ -3767,7 +3335,7 @@ implementation of actual widget type.
Source:
@@ -3874,7 +3442,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4029,7 +3597,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4185,7 +3753,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4386,7 +3954,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4492,7 +4060,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4647,7 +4215,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4802,7 +4370,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4952,7 +4520,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5130,7 +4698,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5308,7 +4876,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5459,7 +5027,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5637,7 +5205,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5811,7 +5379,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5966,7 +5534,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -6120,7 +5688,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -6275,7 +5843,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -6436,7 +6004,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -6596,7 +6164,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -6752,7 +6320,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -6907,7 +6475,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7058,7 +6626,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7213,7 +6781,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7350,7 +6918,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7510,7 +7078,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7670,7 +7238,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7762,7 +7330,7 @@ Typical use case is when new note has been created, we should wait until it is s
Source:
@@ -7832,7 +7400,7 @@ Typical use case is when new note has been created, we should wait until it is s
diff --git a/docs/frontend_api/entities_fattribute.js.html b/docs/frontend_api/entities_fattribute.js.html index dffe972bc..a9cb49a5f 100644 --- a/docs/frontend_api/entities_fattribute.js.html +++ b/docs/frontend_api/entities_fattribute.js.html @@ -121,7 +121,7 @@ export default FAttribute;
diff --git a/docs/frontend_api/entities_fbranch.js.html b/docs/frontend_api/entities_fbranch.js.html index 2b7ced98d..deae06846 100644 --- a/docs/frontend_api/entities_fbranch.js.html +++ b/docs/frontend_api/entities_fbranch.js.html @@ -105,7 +105,7 @@ export default FBranch;
diff --git a/docs/frontend_api/entities_fnote.js.html b/docs/frontend_api/entities_fnote.js.html index a31f615a2..84616e2cb 100644 --- a/docs/frontend_api/entities_fnote.js.html +++ b/docs/frontend_api/entities_fnote.js.html @@ -101,7 +101,7 @@ class FNote { this.mime = row.mime; } - addParent(parentNoteId, branchId) { + addParent(parentNoteId, branchId, sort = true) { if (parentNoteId === 'none') { return; } @@ -111,6 +111,10 @@ class FNote { } this.parentToBranch[parentNoteId] = branchId; + + if (sort) { + this.sortParents(); + } } addChild(childNoteId, branchId, sort = true) { @@ -217,7 +221,7 @@ class FNote { // will sort the parents so that non-search & non-archived are first and archived at the end // this is done so that non-search & non-archived paths are always explored as first when looking for note path - resortParents() { + sortParents() { this.parents.sort((aNoteId, bNoteId) => { const aBranchId = this.parentToBranch[aNoteId]; @@ -225,7 +229,7 @@ class FNote { return 1; } - const aNote = this.froca.getNoteFromCache([aNoteId]); + const aNote = this.froca.getNoteFromCache(aNoteId); if (aNote.isArchived || aNote.isHiddenCompletely()) { return 1; @@ -271,6 +275,11 @@ class FNote { return this.__filterAttrs(this.__getCachedAttributes([]), type, name); } + /** + * @param {string[]} path + * @return {FAttribute[]} + * @private + */ __getCachedAttributes(path) { // notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates // when template instance is a parent of template itself @@ -323,63 +332,49 @@ class FNote { return this.noteId === 'root'; } - getAllNotePaths(encounteredNoteIds = null) { + /** + * Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) + * + * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) + */ + getAllNotePaths() { if (this.noteId === 'root') { return [['root']]; } - if (!encounteredNoteIds) { - encounteredNoteIds = new Set(); + const parentNotes = this.getParentNotes().filter(note => note.type !== 'search'); + let notePaths = []; + + if (parentNotes.length === 1) { // optimization for most common case + notePaths = parentNotes[0].getAllNotePaths(); + } else { + notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths()); } - encounteredNoteIds.add(this.noteId); - - const parentNotes = this.getParentNotes(); - let paths; - - if (parentNotes.length === 1) { // optimization for the most common case - if (encounteredNoteIds.has(parentNotes[0].noteId)) { - return []; - } - else { - paths = parentNotes[0].getAllNotePaths(encounteredNoteIds); - } - } - else { - paths = []; - - for (const parentNote of parentNotes) { - if (encounteredNoteIds.has(parentNote.noteId)) { - continue; - } - - const newSet = new Set(encounteredNoteIds); - - paths.push(...parentNote.getAllNotePaths(newSet)); - } + for (const notePath of notePaths) { + notePath.push(this.noteId); } - for (const path of paths) { - path.push(this.noteId); - } - - return paths; + return notePaths; } - getSortedNotePaths(hoistedNotePath = 'root') { + /** + * @param {string} [hoistedNoteId='root'] + * @return {Array<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array<string>, isHidden: boolean}>} + */ + getSortedNotePathRecords(hoistedNoteId = 'root') { + const isHoistedRoot = hoistedNoteId === 'root'; + const notePaths = this.getAllNotePaths().map(path => ({ notePath: path, - isInHoistedSubTree: path.includes(hoistedNotePath), - isArchived: path.find(noteId => froca.notes[noteId].isArchived), - isSearch: path.find(noteId => froca.notes[noteId].type === 'search'), + isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId), + isArchived: path.some(noteId => froca.notes[noteId].isArchived), isHidden: path.includes('_hidden') })); notePaths.sort((a, b) => { if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { return a.isInHoistedSubTree ? -1 : 1; - } else if (a.isSearch !== b.isSearch) { - return a.isSearch ? 1 : -1; } else if (a.isArchived !== b.isArchived) { return a.isArchived ? 1 : -1; } else if (a.isHidden !== b.isHidden) { @@ -392,6 +387,28 @@ class FNote { return notePaths; } + /** + * Returns note path considered to be the "best" + * + * @param {string} [hoistedNoteId='root'] + * @return {string[]} array of noteIds constituting the particular note path + */ + getBestNotePath(hoistedNoteId = 'root') { + return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath; + } + + /** + * Returns note path considered to be the "best" + * + * @param {string} [hoistedNoteId='root'] + * @return {string} serialized note path (e.g. 'root/a1h315/js725h') + */ + getBestNotePathString(hoistedNoteId = 'root') { + const notePath = this.getBestNotePath(hoistedNoteId); + + return notePath?.join("/"); + } + /** * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree */ @@ -403,7 +420,7 @@ class FNote { for (const parentNote of this.getParentNotes()) { if (parentNote.noteId === 'root') { return false; - } else if (parentNote.noteId === '_hidden') { + } else if (parentNote.noteId === '_hidden' || parentNote.type === 'search') { continue; } @@ -415,6 +432,13 @@ class FNote { return true; } + /** + * @param {FAttribute[]} attributes + * @param {string} type + * @param {string} name + * @return {FAttribute[]} + * @private + */ __filterAttrs(attributes, type, name) { this.__validateTypeName(type, name); @@ -551,7 +575,9 @@ class FNote { * @returns {boolean} true if note has an attribute with given type and name (including inherited) */ hasAttribute(type, name) { - return !!this.getAttribute(type, name); + const attributes = this.getAttributes(); + + return attributes.some(attr => attr.name === name && attr.type === type); } /** @@ -619,6 +645,20 @@ class FNote { */ hasLabel(name) { return this.hasAttribute(LABEL, name); } + /** + * @param {string} name - label name + * @returns {boolean} true if label exists (including inherited) and does not have "false" value. + */ + isLabelTruthy(name) { + const label = this.getLabel(name); + + if (!label) { + return false; + } + + return label && label.value !== 'false'; + } + /** * @param {string} name - relation name * @returns {boolean} true if relation exists (excluding inherited) @@ -730,7 +770,14 @@ class FNote { }); // attrs are not resorted if position changes after initial load - promotedAttrs.sort((a, b) => a.position < b.position ? -1 : 1); + promotedAttrs.sort((a, b) => { + if (a.noteId === b.noteId) { + return a.position < b.position ? -1 : 1; + } else { + // inherited promoted attributes should stay grouped: https://github.com/zadam/trilium/issues/3761 + return a.noteId < b.noteId ? -1 : 1; + } + }); return promotedAttrs; } @@ -930,7 +977,7 @@ export default FNote;
diff --git a/docs/frontend_api/entities_fnote_complement.js.html b/docs/frontend_api/entities_fnote_complement.js.html index 6ea35e95a..252ea6a2f 100644 --- a/docs/frontend_api/entities_fnote_complement.js.html +++ b/docs/frontend_api/entities_fnote_complement.js.html @@ -82,7 +82,7 @@ export default FNoteComplement;
diff --git a/docs/frontend_api/index.html b/docs/frontend_api/index.html index 08a8eb159..6465f193a 100644 --- a/docs/frontend_api/index.html +++ b/docs/frontend_api/index.html @@ -56,7 +56,7 @@
diff --git a/docs/frontend_api/services_frontend_script_api.js.html b/docs/frontend_api/services_frontend_script_api.js.html index a326810c3..679f2e5eb 100644 --- a/docs/frontend_api/services_frontend_script_api.js.html +++ b/docs/frontend_api/services_frontend_script_api.js.html @@ -63,36 +63,12 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain /** @property {dayjs} day.js library for date manipulation. See {@link https://day.js.org} for documentation */ this.dayjs = dayjs; - /** - * @property {RightPanelWidget} - * @deprecated use api.RightPanelWidget instead - */ - this.CollapsibleWidget = RightPanelWidget; - /** @property {RightPanelWidget} */ this.RightPanelWidget = RightPanelWidget; /** @property {NoteContextAwareWidget} */ this.NoteContextAwareWidget = NoteContextAwareWidget; - /** - * @property {NoteContextAwareWidget} - * @deprecated use NoteContextAwareWidget instead - */ - this.TabAwareWidget = NoteContextAwareWidget; - - /** - * @property {NoteContextAwareWidget} - * @deprecated use NoteContextAwareWidget instead - */ - this.TabCachingWidget = NoteContextAwareWidget; - - /** - * @property {NoteContextAwareWidget} - * @deprecated use NoteContextAwareWidget instead - */ - this.NoteContextCachingWidget = NoteContextAwareWidget; - /** @property {BasicWidget} */ this.BasicWidget = BasicWidget; @@ -117,7 +93,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain await ws.waitForMaxKnownEntityChangeId(); await appContext.tabManager.getActiveContext().setNote(notePath); - appContext.triggerEvent('focusAndSelectTitle'); + await appContext.triggerEvent('focusAndSelectTitle'); }; /** @@ -134,7 +110,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain await appContext.tabManager.openContextWithNote(notePath, { activate }); if (activate) { - appContext.triggerEvent('focusAndSelectTitle'); + await appContext.triggerEvent('focusAndSelectTitle'); } }; @@ -152,10 +128,10 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain const subContexts = appContext.tabManager.getActiveContext().getSubContexts(); const {ntxId} = subContexts[subContexts.length - 1]; - appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath}); + await appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath}); if (activate) { - appContext.triggerEvent('focusAndSelectTitle'); + await appContext.triggerEvent('focusAndSelectTitle'); } }; @@ -581,7 +557,7 @@ export default FrontendScriptApi;
diff --git a/package-lock.json b/package-lock.json index 6055a636e..14cc4892b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "dependencies": { "@braintree/sanitize-url": "6.0.2", "@electron/remote": "2.0.9", - "@excalidraw/excalidraw": "0.15.2", + "@excalidraw/excalidraw": "0.14.2", "archiver": "5.3.1", "async-mutex": "0.4.0", "axios": "1.4.0", @@ -462,9 +462,9 @@ } }, "node_modules/@excalidraw/excalidraw": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.15.2.tgz", - "integrity": "sha512-rTI02kgWSTXiUdIkBxt9u/581F3eXcqQgJdIxmz54TFtG3ughoxO5fr4t7Fr2LZIturBPqfocQHGKZ0t2KLKgw==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.14.2.tgz", + "integrity": "sha512-8LdjpTBWEK5waDWB7Bt/G9YBI4j0OxkstUhvaDGz7dwQGfzF6FW5CXBoYHNEoX0qmb+Fg/NPOlZ7FrKsrSVCqg==", "peerDependencies": { "react": "^17.0.2 || ^18.2.0", "react-dom": "^17.0.2 || ^18.2.0" @@ -13591,9 +13591,9 @@ "dev": true }, "@excalidraw/excalidraw": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.15.2.tgz", - "integrity": "sha512-rTI02kgWSTXiUdIkBxt9u/581F3eXcqQgJdIxmz54TFtG3ughoxO5fr4t7Fr2LZIturBPqfocQHGKZ0t2KLKgw==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.14.2.tgz", + "integrity": "sha512-8LdjpTBWEK5waDWB7Bt/G9YBI4j0OxkstUhvaDGz7dwQGfzF6FW5CXBoYHNEoX0qmb+Fg/NPOlZ7FrKsrSVCqg==", "requires": {} }, "@gar/promisify": { diff --git a/package.json b/package.json index a0a42ea5f..3353f12b9 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "dependencies": { "@braintree/sanitize-url": "6.0.2", "@electron/remote": "2.0.9", - "@excalidraw/excalidraw": "0.15.2", + "@excalidraw/excalidraw": "0.14.2", "archiver": "5.3.1", "async-mutex": "0.4.0", "axios": "1.4.0", diff --git a/src/becca/becca_loader.js b/src/becca/becca_loader.js index f7659e964..dd8e990a4 100644 --- a/src/becca/becca_loader.js +++ b/src/becca/becca_loader.js @@ -69,18 +69,6 @@ function reload() { require('../services/ws').reloadFrontend(); } -function postProcessEntityUpdate(entityName, entity) { - if (entityName === 'notes') { - noteUpdated(entity); - } else if (entityName === 'branches') { - branchUpdated(entity); - } else if (entityName === 'attributes') { - attributeUpdated(entity); - } else if (entityName === 'note_reordering') { - noteReorderingUpdated(entity); - } -} - eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entityName, entityRow}) => { if (!becca.loaded) { return; @@ -112,6 +100,25 @@ eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({entityName, en postProcessEntityUpdate(entityName, entity); }); +/** + * This gets run on entity being created or updated. + * + * @param entityName + * @param entityRow - can be a becca entity (change comes from this trilium instance) or just a row (from sync). + * Should be therefore treated as a row. + */ +function postProcessEntityUpdate(entityName, entityRow) { + if (entityName === 'notes') { + noteUpdated(entityRow); + } else if (entityName === 'branches') { + branchUpdated(entityRow); + } else if (entityName === 'attributes') { + attributeUpdated(entityRow); + } else if (entityName === 'note_reordering') { + noteReorderingUpdated(entityRow); + } +} + eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({entityName, entityId}) => { if (!becca.loaded) { return; @@ -149,6 +156,7 @@ function branchDeleted(branchId) { .filter(parentBranch => parentBranch.branchId !== branch.branchId); if (childNote.parents.length > 0) { + // subtree notes might lose some inherited attributes childNote.invalidateSubTree(); } } @@ -163,8 +171,8 @@ function branchDeleted(branchId) { delete becca.branches[branch.branchId]; } -function noteUpdated(entity) { - const note = becca.notes[entity.noteId]; +function noteUpdated(entityRow) { + const note = becca.notes[entityRow.noteId]; if (note) { // type / mime could have been changed, and they are present in flatTextCache @@ -172,15 +180,19 @@ function noteUpdated(entity) { } } -function branchUpdated(branch) { - const childNote = becca.notes[branch.noteId]; +function branchUpdated(branchRow) { + const childNote = becca.notes[branchRow.noteId]; if (childNote) { childNote.flatTextCache = null; childNote.sortParents(); + + // notes in the subtree can get new inherited attributes + // this is in theory needed upon branch creation, but there's no create event for sync changes + childNote.invalidateSubTree(); } - const parentNote = becca.notes[branch.parentNoteId]; + const parentNote = becca.notes[branchRow.parentNoteId]; if (parentNote) { parentNote.sortChildren(); @@ -222,8 +234,10 @@ function attributeDeleted(attributeId) { } } -function attributeUpdated(attribute) { - const note = becca.notes[attribute.noteId]; +/** @param {BAttribute} attributeRow */ +function attributeUpdated(attributeRow) { + const attribute = becca.attributes[attributeRow.attributeId]; + const note = becca.notes[attributeRow.noteId]; if (note) { if (attribute.isAffectingSubtree || note.isInherited()) { diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index b8e58c740..09dd7e679 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -12,6 +12,7 @@ const TaskContext = require("../../services/task_context"); const dayjs = require("dayjs"); const utc = require('dayjs/plugin/utc'); const eventService = require("../../services/events"); +const cls = require("../../services/cls.js"); dayjs.extend(utc); const LABEL = 'label'; @@ -84,7 +85,7 @@ class BNote extends AbstractBeccaEntity { this.decrypt(); /** @type {string|null} */ - this.flatTextCache = null; + this.__flatTextCache = null; return this; } @@ -108,7 +109,7 @@ class BNote extends AbstractBeccaEntity { this.__attributeCache = null; /** @type {BAttribute[]|null} * @private */ - this.inheritableAttributeCache = null; + this.__inheritableAttributeCache = null; /** @type {BAttribute[]} * @private */ @@ -118,7 +119,7 @@ class BNote extends AbstractBeccaEntity { /** @type {BNote[]|null} * @private */ - this.ancestorCache = null; + this.__ancestorCache = null; // following attributes are filled during searching from database @@ -316,10 +317,12 @@ class BNote extends AbstractBeccaEntity { isSynced: true }); - eventService.emit(eventService.ENTITY_CHANGED, { - entityName: 'note_contents', - entity: this - }); + if (!cls.isEntityEventsDisabled()) { + eventService.emit(eventService.ENTITY_CHANGED, { + entityName: 'note_contents', + entity: this + }); + } } setJsonContent(content) { @@ -454,11 +457,11 @@ class BNote extends AbstractBeccaEntity { } } - this.inheritableAttributeCache = []; + this.__inheritableAttributeCache = []; for (const attr of this.__attributeCache) { if (attr.isInheritable) { - this.inheritableAttributeCache.push(attr); + this.__inheritableAttributeCache.push(attr); } } } @@ -475,11 +478,11 @@ class BNote extends AbstractBeccaEntity { return []; } - if (!this.inheritableAttributeCache) { - this.__getAttributes(path); // will refresh also this.inheritableAttributeCache + if (!this.__inheritableAttributeCache) { + this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache } - return this.inheritableAttributeCache; + return this.__inheritableAttributeCache; } __validateTypeName(type, name) { @@ -813,40 +816,40 @@ class BNote extends AbstractBeccaEntity { * @returns {string} - returns flattened textual representation of note, prefixes and attributes */ getFlatText() { - if (!this.flatTextCache) { - this.flatTextCache = `${this.noteId} ${this.type} ${this.mime} `; + if (!this.__flatTextCache) { + 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 += ' '; + this.__flatTextCache += ' '; } - this.flatTextCache = utils.normalize(this.flatTextCache); + this.__flatTextCache = utils.normalize(this.__flatTextCache); } - return this.flatTextCache; + return this.__flatTextCache; } invalidateThisCache() { - this.flatTextCache = null; + this.__flatTextCache = null; this.__attributeCache = null; - this.inheritableAttributeCache = null; - this.ancestorCache = null; + this.__inheritableAttributeCache = null; + this.__ancestorCache = null; } invalidateSubTree(path = []) { @@ -875,24 +878,6 @@ class BNote extends AbstractBeccaEntity { } } - invalidateSubtreeFlatText() { - this.flatTextCache = null; - - for (const childNote of this.children) { - childNote.invalidateSubtreeFlatText(); - } - - for (const targetRelation of this.targetRelations) { - if (targetRelation.name === 'template' || targetRelation.name === 'inherit') { - const note = targetRelation.note; - - if (note) { - note.invalidateSubtreeFlatText(); - } - } - } - } - getRelationDefinitions() { return this.getLabels() .filter(l => l.name.startsWith("relation:")); @@ -1083,28 +1068,28 @@ class BNote extends AbstractBeccaEntity { /** @returns {BNote[]} */ getAncestors() { - if (!this.ancestorCache) { + if (!this.__ancestorCache) { const noteIds = new Set(); - this.ancestorCache = []; + this.__ancestorCache = []; for (const parent of this.parents) { if (noteIds.has(parent.noteId)) { continue; } - this.ancestorCache.push(parent); + this.__ancestorCache.push(parent); noteIds.add(parent.noteId); for (const ancestorNote of parent.getAncestors()) { if (!noteIds.has(ancestorNote.noteId)) { - this.ancestorCache.push(ancestorNote); + this.__ancestorCache.push(ancestorNote); noteIds.add(ancestorNote.noteId); } } } } - return this.ancestorCache; + return this.__ancestorCache; } /** @returns {boolean} */ @@ -1192,7 +1177,7 @@ class BNote extends AbstractBeccaEntity { /** * @param {string} [hoistedNoteId='root'] - * @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]} + * @return {Array<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array, isHidden: boolean}>} */ getSortedNotePathRecords(hoistedNoteId = 'root') { const isHoistedRoot = hoistedNoteId === 'root'; @@ -1491,7 +1476,7 @@ class BNote extends AbstractBeccaEntity { if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { try { this.title = protectedSessionService.decryptString(this.title); - this.flatTextCache = null; + this.__flatTextCache = null; this.isDecrypted = true; } diff --git a/src/etapi/mappers.js b/src/etapi/mappers.js index ad959f36a..86fea9c3d 100644 --- a/src/etapi/mappers.js +++ b/src/etapi/mappers.js @@ -1,3 +1,4 @@ +/** @param {BNote} note */ function mapNoteToPojo(note) { return { noteId: note.noteId, @@ -17,6 +18,7 @@ function mapNoteToPojo(note) { }; } +/** @param {BBranch} branch */ function mapBranchToPojo(branch) { return { branchId: branch.branchId, @@ -29,6 +31,7 @@ function mapBranchToPojo(branch) { }; } +/** @param {BAttribute} attr */ function mapAttributeToPojo(attr) { return { attributeId: attr.attributeId, @@ -46,4 +49,4 @@ module.exports = { mapNoteToPojo, mapBranchToPojo, mapAttributeToPojo -}; \ No newline at end of file +}; diff --git a/src/public/app/entities/fnote.js b/src/public/app/entities/fnote.js index eff96df81..d73f14de2 100644 --- a/src/public/app/entities/fnote.js +++ b/src/public/app/entities/fnote.js @@ -332,7 +332,7 @@ class FNote { /** * @param {string} [hoistedNoteId='root'] - * @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]} + * @return {Array<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array, isHidden: boolean}>} */ getSortedNotePathRecords(hoistedNoteId = 'root') { const isHoistedRoot = hoistedNoteId === 'root'; diff --git a/src/public/app/widgets/title_bar_buttons.js b/src/public/app/widgets/title_bar_buttons.js index 6a4f73ad5..89dd43601 100644 --- a/src/public/app/widgets/title_bar_buttons.js +++ b/src/public/app/widgets/title_bar_buttons.js @@ -56,13 +56,15 @@ export default class TitleBarButtonsWidget extends BasicWidget { const $maximizeBtn = this.$widget.find(".maximize-btn"); const $closeBtn = this.$widget.find(".close-btn"); - //When the window is restarted, the window will not be reset when it is set to the top, so get the window status and set the icon background - (function () { + // When the window is restarted, the window will not be reset when it is set to the top, + // so get the window status and set the icon background + setTimeout(() => { const remote = utils.dynamicRequire('@electron/remote'); - if (remote.BrowserWindow.getFocusedWindow().isAlwaysOnTop()) { + if (remote.BrowserWindow.getFocusedWindow()?.isAlwaysOnTop()) { $topBtn.addClass('active'); } - }()); + }, 1000); + $topBtn.on('click', () => { $topBtn.trigger('blur'); const remote = utils.dynamicRequire('@electron/remote'); diff --git a/src/services/notes.js b/src/services/notes.js index f3d219145..c9343bc4e 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -54,11 +54,10 @@ function deriveMime(type, mime) { } function copyChildAttributes(parentNote, childNote) { - const hasAlreadyTemplate = childNote.hasRelation('template'); - for (const attr of parentNote.getAttributes()) { if (attr.name.startsWith("child:")) { const name = attr.name.substr(6); + const hasAlreadyTemplate = childNote.hasRelation('template'); if (hasAlreadyTemplate && attr.type === 'relation' && name === 'template') { // if the note already has a template, it means the template was chosen by the user explicitly @@ -174,7 +173,7 @@ function createNewNote(params) { // TODO: think about what can happen if the note already exists with the forced ID // I guess on DB it's going to be fine, but becca references between entities - // might get messed up (two Note instance for the same ID existing in the references) + // might get messed up (two note instances for the same ID existing in the references) note = new BNote({ noteId: params.noteId, // optionally can force specific noteId title: params.title, @@ -195,7 +194,7 @@ function createNewNote(params) { } finally { if (!isEntityEventsDisabled) { - // re-enable entity events only if there were previously enabled + // re-enable entity events only if they were previously enabled // (they can be disabled in case of import) cls.enableEntityEvents(); } @@ -215,27 +214,14 @@ function createNewNote(params) { copyChildAttributes(parentNote, note); + eventService.emit(eventService.ENTITY_CREATED, { entityName: 'notes', entity: note }); + eventService.emit(eventService.ENTITY_CHANGED, { entityName: 'notes', entity: note }); triggerNoteTitleChanged(note); - - eventService.emit(eventService.ENTITY_CREATED, { - entityName: 'notes', - entity: note - }); - - eventService.emit(eventService.ENTITY_CREATED, { - entityName: 'note_contents', - entity: note - }); - - eventService.emit(eventService.ENTITY_CREATED, { - entityName: 'branches', - entity: branch - }); - - eventService.emit(eventService.CHILD_NOTE_CREATED, { - childNote: note, - parentNote: parentNote - }); + // note_contents doesn't use "created" event + eventService.emit(eventService.ENTITY_CHANGED, { entityName: 'note_contents', entity: note }); + eventService.emit(eventService.ENTITY_CREATED, { entityName: 'branches', entity: branch }); + eventService.emit(eventService.ENTITY_CHANGED, { entityName: 'branches', entity: branch }); + eventService.emit(eventService.CHILD_NOTE_CREATED, { childNote: note, parentNote: parentNote }); log.info(`Created new note '${note.noteId}', branch '${branch.branchId}' of type '${note.type}', mime '${note.mime}'`); diff --git a/src/share/shaca/entities/snote.js b/src/share/shaca/entities/snote.js index e083ca97b..9c8dbbfcc 100644 --- a/src/share/shaca/entities/snote.js +++ b/src/share/shaca/entities/snote.js @@ -40,7 +40,7 @@ class SNote extends AbstractShacaEntity { /** @param {SAttribute[]|null} */ this.__attributeCache = null; /** @param {SAttribute[]|null} */ - this.inheritableAttributeCache = null; + this.__inheritableAttributeCache = null; /** @param {SAttribute[]} */ this.targetRelations = []; @@ -190,11 +190,11 @@ class SNote extends AbstractShacaEntity { } } - this.inheritableAttributeCache = []; + this.__inheritableAttributeCache = []; for (const attr of this.__attributeCache) { if (attr.isInheritable) { - this.inheritableAttributeCache.push(attr); + this.__inheritableAttributeCache.push(attr); } } } @@ -208,11 +208,11 @@ class SNote extends AbstractShacaEntity { return []; } - if (!this.inheritableAttributeCache) { - this.__getAttributes(path); // will refresh also this.inheritableAttributeCache + if (!this.__inheritableAttributeCache) { + this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache } - return this.inheritableAttributeCache; + return this.__inheritableAttributeCache; } /** @returns {boolean} */ diff --git a/test-etapi/get-inherited-attribute-cloned.http b/test-etapi/get-inherited-attribute-cloned.http new file mode 100644 index 000000000..06c1aa976 --- /dev/null +++ b/test-etapi/get-inherited-attribute-cloned.http @@ -0,0 +1,116 @@ +POST {{triliumHost}}/etapi/create-note +Authorization: {{authToken}} +Content-Type: application/json + +{ + "parentNoteId": "root", + "title": "Hello parent", + "type": "text", + "content": "Hi there!" +} + +> {% +client.assert(response.status === 201); +client.global.set("parentNoteId", response.body.note.noteId); +client.global.set("parentBranchId", response.body.branch.branchId); +%} + +### Create inheritable parent attribute + +POST {{triliumHost}}/etapi/attributes +Authorization: {{authToken}} +Content-Type: application/json + +{ + "noteId": "{{parentNoteId}}", + "type": "label", + "name": "mylabel", + "value": "", + "isInheritable": true, + "position": 10 +} + +> {% +client.assert(response.status === 201); +client.global.set("parentAttributeId", response.body.attributeId); +%} + +### Create child note under root + +POST {{triliumHost}}/etapi/create-note +Authorization: {{authToken}} +Content-Type: application/json + +{ + "parentNoteId": "root", + "title": "Hello child", + "type": "text", + "content": "Hi there!" +} + +> {% +client.assert(response.status === 201); +client.global.set("childNoteId", response.body.note.noteId); +client.global.set("childBranchId", response.body.branch.branchId); +%} + +### Create child attribute + +POST {{triliumHost}}/etapi/attributes +Authorization: {{authToken}} +Content-Type: application/json + +{ + "noteId": "{{childNoteId}}", + "type": "label", + "name": "mylabel", + "value": "val", + "isInheritable": false, + "position": 10 +} + +> {% +client.assert(response.status === 201); +client.global.set("childAttributeId", response.body.attributeId); +%} + +### Clone child to parent + +POST {{triliumHost}}/etapi/branches +Authorization: {{authToken}} +Content-Type: application/json + +{ + "noteId": "{{childNoteId}}", + "parentNoteId": "{{parentNoteId}}" +} + +> {% +client.assert(response.status === 201); +client.assert(response.body.parentNoteId == client.global.get("parentNoteId")); +%} + +### + +GET {{triliumHost}}/etapi/notes/{{childNoteId}} +Authorization: {{authToken}} + +> {% + +function hasAttribute(list, attributeId) { + for (let i = 0; i < list.length; i++) { + if (list[i]["attributeId"] === attributeId) { + return true; + } + } + return false; +} + +client.assert(response.status === 200); +client.assert(response.body.noteId == client.global.get("childNoteId")); +client.assert(response.body.attributes.length == 2); +client.assert(hasAttribute(response.body.attributes, + client.global.get("parentAttributeId"))); +client.assert(hasAttribute(response.body.attributes, + client.global.get("childAttributeId"))); +%} diff --git a/test-etapi/get-inherited-attribute.http b/test-etapi/get-inherited-attribute.http new file mode 100644 index 000000000..d614f419e --- /dev/null +++ b/test-etapi/get-inherited-attribute.http @@ -0,0 +1,44 @@ +POST {{triliumHost}}/etapi/attributes +Authorization: {{authToken}} +Content-Type: application/json + +{ + "noteId": "root", + "type": "label", + "name": "mylabel", + "value": "val", + "isInheritable": true +} + +> {% client.global.set("createdAttributeId", response.body.attributeId); %} + +### + +POST {{triliumHost}}/etapi/create-note +Authorization: {{authToken}} +Content-Type: application/json + +{ + "parentNoteId": "root", + "title": "Hello", + "type": "text", + "content": "Hi there!" +} + +> {% +client.global.set("createdNoteId", response.body.note.noteId); +client.global.set("createdBranchId", response.body.branch.branchId); +%} + +### + +GET {{triliumHost}}/etapi/notes/{{createdNoteId}} +Authorization: {{authToken}} + +> {% +client.assert(response.status === 200); +client.assert(response.body.noteId == client.global.get("createdNoteId")); +client.assert(response.body.attributes.length == 1); +client.assert(response.body.attributes[0].attributeId == + client.global.get("createdAttributeId")); +%}