From a9dae7823faf75cd860e1687f37ef1dd0c8bbe8d Mon Sep 17 00:00:00 2001 From: zadam Date: Wed, 31 May 2023 14:00:37 +0200 Subject: [PATCH 01/10] script api docs --- docs/backend_api/AbstractBeccaEntity.html | 2 +- docs/backend_api/BAttribute.html | 2 +- docs/backend_api/BBranch.html | 2 +- docs/backend_api/BEtapiToken.html | 2 +- docs/backend_api/BNote.html | 792 ++++++++++++++++-- docs/backend_api/BNoteRevision.html | 2 +- docs/backend_api/BOption.html | 10 +- docs/backend_api/BRecentNote.html | 2 +- docs/backend_api/BackendScriptApi.html | 8 +- ...cca_entities_abstract_becca_entity.js.html | 2 +- .../becca_entities_battribute.js.html | 4 +- .../becca_entities_bbranch.js.html | 2 +- .../becca_entities_betapi_token.js.html | 2 +- docs/backend_api/becca_entities_bnote.js.html | 112 ++- .../becca_entities_bnote_revision.js.html | 4 +- .../becca_entities_boption.js.html | 9 +- .../becca_entities_brecent_note.js.html | 2 +- docs/backend_api/index.html | 2 +- docs/backend_api/module-sql.html | 2 +- .../services_backend_script_api.js.html | 12 +- docs/backend_api/services_sql.js.html | 6 +- package-lock.json | 1 - src/becca/entities/bnote.js | 2 +- src/public/app/entities/fnote.js | 2 +- 24 files changed, 881 insertions(+), 105 deletions(-) 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/package-lock.json b/package-lock.json index 6055a636e..682c4f5a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "trilium", "version": "0.60.1-beta", "hasInstallScript": true, "license": "AGPL-3.0-only", diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index b8e58c740..0be9a3ab4 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -1192,7 +1192,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'; 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'; From a6ade790c64ad9ee68f63d53743e0f90c7898672 Mon Sep 17 00:00:00 2001 From: zadam Date: Wed, 31 May 2023 14:35:08 +0200 Subject: [PATCH 02/10] script api docs --- docs/frontend_api/FAttribute.html | 2 +- docs/frontend_api/FBranch.html | 2 +- docs/frontend_api/FNote.html | 888 +++++++++++++++++- docs/frontend_api/FNoteComplement.html | 2 +- docs/frontend_api/FrontendScriptApi.html | 520 +--------- docs/frontend_api/entities_fattribute.js.html | 2 +- docs/frontend_api/entities_fbranch.js.html | 2 +- docs/frontend_api/entities_fnote.js.html | 139 ++- .../entities_fnote_complement.js.html | 2 +- docs/frontend_api/index.html | 2 +- .../services_frontend_script_api.js.html | 34 +- 11 files changed, 990 insertions(+), 605 deletions(-) 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;
From 9e5a02094ca93ca9b09dfffdf7d72a976630aa81 Mon Sep 17 00:00:00 2001 From: mm21 <8033134+mm21@users.noreply.github.com> Date: Wed, 31 May 2023 17:11:42 +0000 Subject: [PATCH 03/10] Add ETAPI tests to repro inherited attribute issue and workaround --- .../get-inherited-attribute-workaround.http | 67 +++++++++++++++++++ test-etapi/get-inherited-attribute.http | 44 ++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 test-etapi/get-inherited-attribute-workaround.http create mode 100644 test-etapi/get-inherited-attribute.http diff --git a/test-etapi/get-inherited-attribute-workaround.http b/test-etapi/get-inherited-attribute-workaround.http new file mode 100644 index 000000000..527f11e69 --- /dev/null +++ b/test-etapi/get-inherited-attribute-workaround.http @@ -0,0 +1,67 @@ +POST {{triliumHost}}/etapi/attributes +Authorization: {{authToken}} +Content-Type: application/json + +{ + "noteId": "root", + "type": "label", + "name": "mylabel", + "value": "val", + "isInheritable": true +} + +> {% client.global.set("createdInheritedAttributeId", 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); +%} + +### + +POST {{triliumHost}}/etapi/attributes +Authorization: {{authToken}} +Content-Type: application/json + +{ + "noteId": "{{createdNoteId}}", + "type": "label", + "name": "mylabel", + "value": "val", + "isInheritable": false +} + +> {% client.global.set("createdOwnedAttributeId", response.body.attributeId); %} + +### + +DELETE {{triliumHost}}/etapi/attributes/{{createdOwnedAttributeId}} +Authorization: {{authToken}} + +> {% client.assert(response.status === 204); %} + +### + +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("createdInheritedAttributeId")); +%} 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")); +%} From b5cfc28912404b026825f76eb96b3dce04f6c905 Mon Sep 17 00:00:00 2001 From: zadam Date: Wed, 31 May 2023 23:21:06 +0200 Subject: [PATCH 04/10] rename inheritableAttributeCache to __inheritableAttributeCache for consistency --- src/becca/entities/bnote.js | 14 ++-- src/etapi/mappers.js | 5 +- src/share/shaca/entities/snote.js | 12 ++-- .../get-inherited-attribute-workaround.http | 67 ------------------- 4 files changed, 17 insertions(+), 81 deletions(-) delete mode 100644 test-etapi/get-inherited-attribute-workaround.http diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index 0be9a3ab4..ec24fe08e 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -108,7 +108,7 @@ class BNote extends AbstractBeccaEntity { this.__attributeCache = null; /** @type {BAttribute[]|null} * @private */ - this.inheritableAttributeCache = null; + this.__inheritableAttributeCache = null; /** @type {BAttribute[]} * @private */ @@ -454,11 +454,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 +475,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) { @@ -845,7 +845,7 @@ class BNote extends AbstractBeccaEntity { this.flatTextCache = null; this.__attributeCache = null; - this.inheritableAttributeCache = null; + this.__inheritableAttributeCache = null; this.ancestorCache = null; } 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/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-workaround.http b/test-etapi/get-inherited-attribute-workaround.http deleted file mode 100644 index 527f11e69..000000000 --- a/test-etapi/get-inherited-attribute-workaround.http +++ /dev/null @@ -1,67 +0,0 @@ -POST {{triliumHost}}/etapi/attributes -Authorization: {{authToken}} -Content-Type: application/json - -{ - "noteId": "root", - "type": "label", - "name": "mylabel", - "value": "val", - "isInheritable": true -} - -> {% client.global.set("createdInheritedAttributeId", 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); -%} - -### - -POST {{triliumHost}}/etapi/attributes -Authorization: {{authToken}} -Content-Type: application/json - -{ - "noteId": "{{createdNoteId}}", - "type": "label", - "name": "mylabel", - "value": "val", - "isInheritable": false -} - -> {% client.global.set("createdOwnedAttributeId", response.body.attributeId); %} - -### - -DELETE {{triliumHost}}/etapi/attributes/{{createdOwnedAttributeId}} -Authorization: {{authToken}} - -> {% client.assert(response.status === 204); %} - -### - -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("createdInheritedAttributeId")); -%} From ae42e0efc77e468afd6b1b0d84619574f68fb5e6 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 1 Jun 2023 00:07:57 +0200 Subject: [PATCH 05/10] refactoring --- src/becca/becca_loader.js | 33 +++++++----- src/becca/entities/bnote.js | 56 +++++++-------------- src/public/app/widgets/title_bar_buttons.js | 10 ++-- src/services/notes.js | 34 ++++--------- 4 files changed, 56 insertions(+), 77 deletions(-) diff --git a/src/becca/becca_loader.js b/src/becca/becca_loader.js index f7659e964..82dd09e42 100644 --- a/src/becca/becca_loader.js +++ b/src/becca/becca_loader.js @@ -69,15 +69,22 @@ function reload() { require('../services/ws').reloadFrontend(); } -function 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(entity); + noteUpdated(entityRow); } else if (entityName === 'branches') { - branchUpdated(entity); + branchUpdated(entityRow); } else if (entityName === 'attributes') { - attributeUpdated(entity); + attributeUpdated(entityRow); } else if (entityName === 'note_reordering') { - noteReorderingUpdated(entity); + noteReorderingUpdated(entityRow); } } @@ -163,8 +170,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 +179,15 @@ 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(); } - const parentNote = becca.notes[branch.parentNoteId]; + const parentNote = becca.notes[branchRow.parentNoteId]; if (parentNote) { parentNote.sortChildren(); @@ -222,8 +229,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 ec24fe08e..657de1e66 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -84,7 +84,7 @@ class BNote extends AbstractBeccaEntity { this.decrypt(); /** @type {string|null} */ - this.flatTextCache = null; + this.__flatTextCache = null; return this; } @@ -118,7 +118,7 @@ class BNote extends AbstractBeccaEntity { /** @type {BNote[]|null} * @private */ - this.ancestorCache = null; + this.__ancestorCache = null; // following attributes are filled during searching from database @@ -813,40 +813,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.__ancestorCache = null; } invalidateSubTree(path = []) { @@ -875,24 +875,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 +1065,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} */ @@ -1491,7 +1473,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/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}'`); From 758dba9ba547e1d178851677156c9b39ab472417 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 1 Jun 2023 00:13:08 +0200 Subject: [PATCH 06/10] fix event --- src/becca/entities/bnote.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index 657de1e66..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'; @@ -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) { From 86a080e7ecbd332f95688de971921b903bda87a0 Mon Sep 17 00:00:00 2001 From: mm21 <8033134+mm21@users.noreply.github.com> Date: Thu, 1 Jun 2023 17:20:11 +0000 Subject: [PATCH 07/10] Add ETAPI test to repro inherited attribute issue for cloned note #3994 --- ...inherited-attribute-cloned-workaround.http | 142 ++++++++++++++++++ .../get-inherited-attribute-cloned.http | 116 ++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 test-etapi/get-inherited-attribute-cloned-workaround.http create mode 100644 test-etapi/get-inherited-attribute-cloned.http diff --git a/test-etapi/get-inherited-attribute-cloned-workaround.http b/test-etapi/get-inherited-attribute-cloned-workaround.http new file mode 100644 index 000000000..3bd88402c --- /dev/null +++ b/test-etapi/get-inherited-attribute-cloned-workaround.http @@ -0,0 +1,142 @@ +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")); +%} + +### Workaround: create dummy attribute + +POST {{triliumHost}}/etapi/attributes +Authorization: {{authToken}} +Content-Type: application/json + +{ + "noteId": "{{childNoteId}}", + "type": "label", + "name": "childlabel", + "value": "val", + "isInheritable": false +} + +> {% +client.assert(response.status === 201); +client.global.set("dummyAttributeId", response.body.attributeId); +%} + +### Workaround: delete dummy attribute + +DELETE {{triliumHost}}/etapi/attributes/{{dummyAttributeId}} +Authorization: {{authToken}} + +> {% client.assert(response.status === 204); %} + +### + +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-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"))); +%} From d9359c7c558dd5cec293d4fe4a4294cc5ebbaee7 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 1 Jun 2023 22:51:34 +0200 Subject: [PATCH 08/10] downgrade excalidraw to 0.14.2, #3979 --- package-lock.json | 15 ++++++++------- package.json | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 682c4f5a0..14cc4892b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,13 +5,14 @@ "requires": true, "packages": { "": { + "name": "trilium", "version": "0.60.1-beta", "hasInstallScript": true, "license": "AGPL-3.0-only", "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", @@ -461,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" @@ -13590,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", From 0c4492bcd033505c03dcb332d639d978262096e3 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 1 Jun 2023 23:14:17 +0200 Subject: [PATCH 09/10] invalidate attribute cache on branch create/update, fixes #3994 --- src/becca/becca_loader.js | 43 ++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/becca/becca_loader.js b/src/becca/becca_loader.js index 82dd09e42..dd8e990a4 100644 --- a/src/becca/becca_loader.js +++ b/src/becca/becca_loader.js @@ -69,25 +69,6 @@ function reload() { require('../services/ws').reloadFrontend(); } -/** - * 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_CHANGE_SYNCED], ({entityName, entityRow}) => { if (!becca.loaded) { return; @@ -119,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; @@ -156,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(); } } @@ -185,6 +186,10 @@ function branchUpdated(branchRow) { 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[branchRow.parentNoteId]; From e47c3a27b0eb1966f8a791901948485301df1e57 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 1 Jun 2023 23:18:50 +0200 Subject: [PATCH 10/10] remove the workaround test --- ...inherited-attribute-cloned-workaround.http | 142 ------------------ 1 file changed, 142 deletions(-) delete mode 100644 test-etapi/get-inherited-attribute-cloned-workaround.http diff --git a/test-etapi/get-inherited-attribute-cloned-workaround.http b/test-etapi/get-inherited-attribute-cloned-workaround.http deleted file mode 100644 index 3bd88402c..000000000 --- a/test-etapi/get-inherited-attribute-cloned-workaround.http +++ /dev/null @@ -1,142 +0,0 @@ -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")); -%} - -### Workaround: create dummy attribute - -POST {{triliumHost}}/etapi/attributes -Authorization: {{authToken}} -Content-Type: application/json - -{ - "noteId": "{{childNoteId}}", - "type": "label", - "name": "childlabel", - "value": "val", - "isInheritable": false -} - -> {% -client.assert(response.status === 201); -client.global.set("dummyAttributeId", response.body.attributeId); -%} - -### Workaround: delete dummy attribute - -DELETE {{triliumHost}}/etapi/attributes/{{dummyAttributeId}} -Authorization: {{authToken}} - -> {% client.assert(response.status === 204); %} - -### - -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"))); -%}