From 643a5e5b169ffeae2e5f4ef53b394777557e48c1 Mon Sep 17 00:00:00 2001 From: zadam Date: Tue, 19 Apr 2022 23:06:46 +0200 Subject: [PATCH 01/14] moving `deleteNote` and `deleteBranch` into entities to make them accessible to scripts, #2792 --- docs/backend_api/AbstractEntity.html | 4 +- docs/backend_api/Attribute.html | 4 +- docs/backend_api/Branch.html | 228 +++++++++++- docs/backend_api/EtapiToken.html | 4 +- docs/backend_api/Note.html | 346 ++++++++++++++---- docs/backend_api/NoteRevision.html | 4 +- docs/backend_api/Option.html | 4 +- docs/backend_api/RecentNote.html | 4 +- .../becca_entities_abstract_entity.js.html | 2 + .../backend_api/becca_entities_branch.js.html | 61 +++ docs/backend_api/becca_entities_note.js.html | 21 ++ package-lock.json | 78 ++-- package.json | 6 +- src/becca/entities/abstract_entity.js | 2 + src/becca/entities/branch.js | 61 +++ src/becca/entities/note.js | 21 ++ src/etapi/branches.js | 2 +- src/etapi/notes.js | 2 +- src/routes/api/branches.js | 2 +- src/routes/api/notes.js | 2 +- src/services/cloning.js | 3 +- src/services/notes.js | 66 +--- 22 files changed, 734 insertions(+), 193 deletions(-) diff --git a/docs/backend_api/AbstractEntity.html b/docs/backend_api/AbstractEntity.html index e017cf1c9..ab08501ce 100644 --- a/docs/backend_api/AbstractEntity.html +++ b/docs/backend_api/AbstractEntity.html @@ -158,6 +158,8 @@
Mark the entity as (soft) deleted. It will be completely erased later. + +This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
@@ -265,7 +267,7 @@
Source:
diff --git a/docs/backend_api/Attribute.html b/docs/backend_api/Attribute.html index 59b518036..8633da77b 100644 --- a/docs/backend_api/Attribute.html +++ b/docs/backend_api/Attribute.html @@ -1030,6 +1030,8 @@ and relation (representing named relationship between source and target note) Mark the entity as (soft) deleted. It will be completely erased later. + +This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead. @@ -1142,7 +1144,7 @@ and relation (representing named relationship between source and target note)Source:
diff --git a/docs/backend_api/Branch.html b/docs/backend_api/Branch.html index 4be4b47f5..f89ff45ad 100644 --- a/docs/backend_api/Branch.html +++ b/docs/backend_api/Branch.html @@ -94,7 +94,7 @@ parents.
Source:
@@ -205,7 +205,7 @@ parents.
Source:
@@ -263,7 +263,7 @@ parents.
Source:
@@ -331,7 +331,7 @@ parents.
Source:
@@ -399,7 +399,7 @@ parents.
Source:
@@ -467,7 +467,7 @@ parents.
Source:
@@ -525,7 +525,7 @@ parents.
Source:
@@ -593,7 +593,7 @@ parents.
Source:
@@ -661,7 +661,7 @@ parents.
Source:
@@ -729,7 +729,7 @@ parents.
Source:
@@ -757,6 +757,210 @@ parents. +

deleteBranch(deleteIdopt, taskContextopt) → {boolean}

+ + + + + + +
+ Delete a branch. If this is a last note's branch, delete the note as well. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
deleteId + + +string + + + + + + <optional>
+ + + + + +
optional delete identified
taskContext + + +TaskContext + + + + + + <optional>
+ + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ - true if note has been deleted, false otherwise +
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + + + +

markAsDeleted(deleteIdopt)

@@ -766,6 +970,8 @@ parents.
Mark the entity as (soft) deleted. It will be completely erased later. + +This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
@@ -878,7 +1084,7 @@ parents.
Source:
diff --git a/docs/backend_api/EtapiToken.html b/docs/backend_api/EtapiToken.html index bffcb3658..b675d8cdc 100644 --- a/docs/backend_api/EtapiToken.html +++ b/docs/backend_api/EtapiToken.html @@ -587,6 +587,8 @@ from tokenHash and token.
Mark the entity as (soft) deleted. It will be completely erased later. + +This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
@@ -699,7 +701,7 @@ from tokenHash and token.
Source:
diff --git a/docs/backend_api/Note.html b/docs/backend_api/Note.html index 5a24d464a..c5eea4f2f 100644 --- a/docs/backend_api/Note.html +++ b/docs/backend_api/Note.html @@ -93,7 +93,7 @@
Source:
@@ -204,7 +204,7 @@
Source:
@@ -279,7 +279,7 @@
Source:
@@ -347,7 +347,7 @@
Source:
@@ -415,7 +415,7 @@
Source:
@@ -486,7 +486,7 @@
Source:
@@ -554,7 +554,7 @@
Source:
@@ -622,7 +622,7 @@
Source:
@@ -690,7 +690,7 @@
Source:
@@ -758,7 +758,7 @@
Source:
@@ -833,7 +833,7 @@
Source:
@@ -901,7 +901,7 @@
Source:
@@ -969,7 +969,7 @@
Source:
@@ -1037,7 +1037,7 @@
Source:
@@ -1112,7 +1112,7 @@
Source:
@@ -1180,7 +1180,7 @@
Source:
@@ -1248,7 +1248,7 @@
Source:
@@ -1316,7 +1316,7 @@
Source:
@@ -1384,7 +1384,7 @@
Source:
@@ -1452,7 +1452,7 @@
Source:
@@ -1528,7 +1528,7 @@
Source:
@@ -1630,7 +1630,7 @@
Source:
@@ -1678,6 +1678,188 @@ + + + + + + +

deleteNote(deleteIdopt, taskContextopt)

+ + + + + + +
+ (Soft) delete a note and all its descendants. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
deleteId + + +string + + + + + + <optional>
+ + + + + +
optional delete identified
taskContext + + +TaskContext + + + + + + <optional>
+ + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + @@ -1732,7 +1914,7 @@
Source:
@@ -1838,7 +2020,7 @@
Source:
@@ -2012,7 +2194,7 @@
Source:
@@ -2212,7 +2394,7 @@
Source:
@@ -2390,7 +2572,7 @@
Source:
@@ -2501,7 +2683,7 @@
Source:
@@ -2603,7 +2785,7 @@
Source:
@@ -2705,7 +2887,7 @@
Source:
@@ -2807,7 +2989,7 @@
Source:
@@ -2909,7 +3091,7 @@
Source:
@@ -3017,7 +3199,7 @@
Source:
@@ -3123,7 +3305,7 @@
Source:
@@ -3274,7 +3456,7 @@
Source:
@@ -3444,7 +3626,7 @@
Source:
@@ -3599,7 +3781,7 @@
Source:
@@ -3769,7 +3951,7 @@
Source:
@@ -3875,7 +4057,7 @@
Source:
@@ -4077,7 +4259,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -4255,7 +4437,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -4413,7 +4595,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -4583,7 +4765,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -4738,7 +4920,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -4908,7 +5090,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -5063,7 +5245,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -5233,7 +5415,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -5388,7 +5570,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -5497,7 +5679,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -5599,7 +5781,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -5750,7 +5932,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -5920,7 +6102,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -6075,7 +6257,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -6184,7 +6366,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -6293,7 +6475,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -6395,7 +6577,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -6497,7 +6679,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -6599,7 +6781,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -6706,7 +6888,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -6808,7 +6990,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -6959,7 +7141,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -7137,7 +7319,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -7292,7 +7474,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -7447,7 +7629,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -7602,7 +7784,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -7752,7 +7934,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -7858,7 +8040,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -7964,7 +8146,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -8070,7 +8252,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -8176,7 +8358,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -8282,7 +8464,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -8349,6 +8531,8 @@ This method can be significantly faster than the getAttribute()
Mark the entity as (soft) deleted. It will be completely erased later. + +This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
@@ -8461,7 +8645,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -8672,7 +8856,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -8852,7 +9036,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -9032,7 +9216,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -9354,7 +9538,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -9534,7 +9718,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -9694,7 +9878,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -9936,7 +10120,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -10147,7 +10331,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -10358,7 +10542,7 @@ This method can be significantly faster than the getAttribute()
Source:
diff --git a/docs/backend_api/NoteRevision.html b/docs/backend_api/NoteRevision.html index f9575d7df..ad8b80285 100644 --- a/docs/backend_api/NoteRevision.html +++ b/docs/backend_api/NoteRevision.html @@ -1300,6 +1300,8 @@ It's used for seamless note versioning.
Mark the entity as (soft) deleted. It will be completely erased later. + +This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
@@ -1412,7 +1414,7 @@ It's used for seamless note versioning.
Source:
diff --git a/docs/backend_api/Option.html b/docs/backend_api/Option.html index 2c43460c7..c812d8ea6 100644 --- a/docs/backend_api/Option.html +++ b/docs/backend_api/Option.html @@ -445,6 +445,8 @@
Mark the entity as (soft) deleted. It will be completely erased later. + +This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
@@ -557,7 +559,7 @@
Source:
diff --git a/docs/backend_api/RecentNote.html b/docs/backend_api/RecentNote.html index 264580c72..621598ed2 100644 --- a/docs/backend_api/RecentNote.html +++ b/docs/backend_api/RecentNote.html @@ -377,6 +377,8 @@
Mark the entity as (soft) deleted. It will be completely erased later. + +This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
@@ -489,7 +491,7 @@
Source:
diff --git a/docs/backend_api/becca_entities_abstract_entity.js.html b/docs/backend_api/becca_entities_abstract_entity.js.html index 43b0899a8..a66b5654a 100644 --- a/docs/backend_api/becca_entities_abstract_entity.js.html +++ b/docs/backend_api/becca_entities_abstract_entity.js.html @@ -139,6 +139,8 @@ class AbstractEntity { /** * Mark the entity as (soft) deleted. It will be completely erased later. * + * This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead. + * * @param [deleteId=null] */ markAsDeleted(deleteId = null) { diff --git a/docs/backend_api/becca_entities_branch.js.html b/docs/backend_api/becca_entities_branch.js.html index 94ffed9dc..73fd62a3b 100644 --- a/docs/backend_api/becca_entities_branch.js.html +++ b/docs/backend_api/becca_entities_branch.js.html @@ -32,6 +32,10 @@ const Note = require('./note'); const AbstractEntity = require("./abstract_entity"); const sql = require("../../services/sql"); const dateUtils = require("../../services/date_utils"); +const utils = require("../../services/utils.js"); +const TaskContext = require("../../services/task_context.js"); +const cls = require("../../services/cls.js"); +const log = require("../../services/log.js"); /** * Branch represents a relationship between a child note and its parent note. Trilium allows a note to have multiple @@ -142,6 +146,63 @@ class Branch extends AbstractEntity { return !(this.branchId in this.becca.branches); } + /** + * Delete a branch. If this is a last note's branch, delete the note as well. + * + * @param {string} [deleteId] - optional delete identified + * @param {TaskContext} [taskContext] + * + * @return {boolean} - true if note has been deleted, false otherwise + */ + deleteBranch(deleteId, taskContext) { + if (!deleteId) { + deleteId = utils.randomString(10); + } + + if (!taskContext) { + taskContext = new TaskContext('no-progress-reporting'); + } + + taskContext.increaseProgressCount(); + + if (this.branchId === 'root' + || this.noteId === 'root' + || this.noteId === cls.getHoistedNoteId()) { + + throw new Error("Can't delete root or hoisted branch/note"); + } + + this.markAsDeleted(deleteId); + + const note = this.getNote(); + const notDeletedBranches = note.getParentBranches(); + + if (notDeletedBranches.length === 0) { + for (const childBranch of note.getChildBranches()) { + childBranch.deleteBranch(deleteId, taskContext); + } + + // first delete children and then parent - this will show up better in recent changes + + log.info("Deleting note " + note.noteId); + + for (const attribute of note.getOwnedAttributes()) { + attribute.markAsDeleted(deleteId); + } + + for (const relation of note.getTargetRelations()) { + relation.markAsDeleted(deleteId); + } + + note.markAsDeleted(deleteId); + + return true; + } + else { + return false; + } + } + beforeSaving() { if (this.notePosition === undefined || this.notePosition === null) { // TODO finding new position can be refactored into becca diff --git a/docs/backend_api/becca_entities_note.js.html b/docs/backend_api/becca_entities_note.js.html index 2aae3ae51..ad2571123 100644 --- a/docs/backend_api/becca_entities_note.js.html +++ b/docs/backend_api/becca_entities_note.js.html @@ -36,6 +36,7 @@ const dateUtils = require('../../services/date_utils'); const entityChangesService = require('../../services/entity_changes'); const AbstractEntity = require("./abstract_entity"); const NoteRevision = require("./note_revision"); +const TaskContext = require("../../services/task_context.js"); const LABEL = 'label'; const RELATION = 'relation'; @@ -1153,6 +1154,26 @@ class Note extends AbstractEntity { return cloningService.cloneNoteToBranch(this.noteId, branch.branchId); } + /** + * (Soft) delete a note and all its descendants. + * + * @param {string} [deleteId] - optional delete identified + * @param {TaskContext} [taskContext] + */ + deleteNote(deleteId, taskContext) { + if (!deleteId) { + deleteId = utils.randomString(10); + } + + if (!taskContext) { + taskContext = new TaskContext('no-progress-reporting'); + } + + for (const branch of this.getParentBranches()) { + branch.deleteBranch(deleteId, taskContext); + } + } + decrypt() { if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { try { diff --git a/package-lock.json b/package-lock.json index 93ccebfa5..f82666a52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "trilium", "version": "0.51.0-beta", "license": "AGPL-3.0-only", "dependencies": { @@ -28,7 +29,7 @@ "express-partial-content": "1.0.2", "express-rate-limit": "6.3.0", "express-session": "1.17.2", - "fs-extra": "10.0.1", + "fs-extra": "10.1.0", "helmet": "5.0.2", "html": "1.0.0", "html2plaintext": "2.1.4", @@ -43,7 +44,7 @@ "jsdom": "19.0.0", "mime-types": "2.1.35", "multer": "1.4.4", - "node-abi": "3.8.0", + "node-abi": "3.15.0", "normalize-strings": "1.1.1", "open": "8.4.0", "portscanner": "2.2.0", @@ -71,7 +72,7 @@ "cross-env": "7.0.3", "electron": "16.2.1", "electron-builder": "23.0.3", - "electron-packager": "15.4.0", + "electron-packager": "15.5.0", "electron-rebuild": "3.2.7", "esm": "3.2.25", "jasmine": "4.1.0", @@ -4130,12 +4131,13 @@ } }, "node_modules/electron-packager": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-15.4.0.tgz", - "integrity": "sha512-JrrLcBP15KGrPj0cZ/ALKGmaQ4gJkn3mocf0E3bRKdR3kxKWYcDRpCvdhksYDXw/r3I6tMEcZ7XzyApWFXdVpw==", + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-15.5.0.tgz", + "integrity": "sha512-8mITLQgTm9xdrO8XL/PsK0EZGU7zK/ay7TI8M1C9pc1UZ++HlaWQJBRJHlOXf4TL/7FsiF4OciEhiqhMn+LKQQ==", "dev": true, "dependencies": { "@electron/get": "^1.6.0", + "@electron/universal": "^1.2.1", "asar": "^3.1.0", "cross-spawn-windows-exe": "^1.2.0", "debug": "^4.0.1", @@ -4164,6 +4166,24 @@ "url": "https://github.com/electron/electron-packager?sponsor=1" } }, + "node_modules/electron-packager/node_modules/@electron/universal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.2.1.tgz", + "integrity": "sha512-7323HyMh7KBAl/nPDppdLsC87G6RwRU02dy5FPeGB1eS7rUePh55+WNWiDPLhFQqqVPHzh77M69uhmoT8XnwMQ==", + "dev": true, + "dependencies": { + "@malept/cross-spawn-promise": "^1.1.0", + "asar": "^3.1.0", + "debug": "^4.3.1", + "dir-compare": "^2.4.0", + "fs-extra": "^9.0.1", + "minimatch": "^3.0.4", + "plist": "^3.0.4" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/electron-packager/node_modules/cross-spawn-windows-exe": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/cross-spawn-windows-exe/-/cross-spawn-windows-exe-1.2.0.tgz", @@ -5576,9 +5596,9 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "node_modules/fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -7661,9 +7681,9 @@ "dev": true }, "node_modules/node-abi": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.8.0.tgz", - "integrity": "sha512-tzua9qWWi7iW4I42vUPKM+SfaF0vQSLAm4yO5J83mSwB7GeoWrDKC/K+8YCnYNwqP5duwazbw2X9l4m8SC2cUw==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.15.0.tgz", + "integrity": "sha512-Ic6z/j6I9RLm4ov7npo1I48UQr2BEyFCqh6p7S1dhEx9jPO0GPGq/e2Rb7x7DroQrmiVMz/Bw1vJm9sPAl2nxA==", "dependencies": { "semver": "^7.3.5" }, @@ -14556,12 +14576,13 @@ } }, "electron-packager": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-15.4.0.tgz", - "integrity": "sha512-JrrLcBP15KGrPj0cZ/ALKGmaQ4gJkn3mocf0E3bRKdR3kxKWYcDRpCvdhksYDXw/r3I6tMEcZ7XzyApWFXdVpw==", + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-15.5.0.tgz", + "integrity": "sha512-8mITLQgTm9xdrO8XL/PsK0EZGU7zK/ay7TI8M1C9pc1UZ++HlaWQJBRJHlOXf4TL/7FsiF4OciEhiqhMn+LKQQ==", "dev": true, "requires": { "@electron/get": "^1.6.0", + "@electron/universal": "^1.2.1", "asar": "^3.1.0", "cross-spawn-windows-exe": "^1.2.0", "debug": "^4.0.1", @@ -14581,6 +14602,21 @@ "yargs-parser": "^20.0.0" }, "dependencies": { + "@electron/universal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.2.1.tgz", + "integrity": "sha512-7323HyMh7KBAl/nPDppdLsC87G6RwRU02dy5FPeGB1eS7rUePh55+WNWiDPLhFQqqVPHzh77M69uhmoT8XnwMQ==", + "dev": true, + "requires": { + "@malept/cross-spawn-promise": "^1.1.0", + "asar": "^3.1.0", + "debug": "^4.3.1", + "dir-compare": "^2.4.0", + "fs-extra": "^9.0.1", + "minimatch": "^3.0.4", + "plist": "^3.0.4" + } + }, "cross-spawn-windows-exe": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/cross-spawn-windows-exe/-/cross-spawn-windows-exe-1.2.0.tgz", @@ -15507,9 +15543,9 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -17133,9 +17169,9 @@ "dev": true }, "node-abi": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.8.0.tgz", - "integrity": "sha512-tzua9qWWi7iW4I42vUPKM+SfaF0vQSLAm4yO5J83mSwB7GeoWrDKC/K+8YCnYNwqP5duwazbw2X9l4m8SC2cUw==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.15.0.tgz", + "integrity": "sha512-Ic6z/j6I9RLm4ov7npo1I48UQr2BEyFCqh6p7S1dhEx9jPO0GPGq/e2Rb7x7DroQrmiVMz/Bw1vJm9sPAl2nxA==", "requires": { "semver": "^7.3.5" } diff --git a/package.json b/package.json index 1ad93025a..3a0356b2d 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "express-partial-content": "1.0.2", "express-rate-limit": "6.3.0", "express-session": "1.17.2", - "fs-extra": "10.0.1", + "fs-extra": "10.1.0", "helmet": "5.0.2", "html": "1.0.0", "html2plaintext": "2.1.4", @@ -59,7 +59,7 @@ "jsdom": "19.0.0", "mime-types": "2.1.35", "multer": "1.4.4", - "node-abi": "3.8.0", + "node-abi": "3.15.0", "normalize-strings": "1.1.1", "open": "8.4.0", "portscanner": "2.2.0", @@ -84,7 +84,7 @@ "cross-env": "7.0.3", "electron": "16.2.1", "electron-builder": "23.0.3", - "electron-packager": "15.4.0", + "electron-packager": "15.5.0", "electron-rebuild": "3.2.7", "esm": "3.2.25", "jasmine": "4.1.0", diff --git a/src/becca/entities/abstract_entity.js b/src/becca/entities/abstract_entity.js index 9981373c3..574c5bae5 100644 --- a/src/becca/entities/abstract_entity.js +++ b/src/becca/entities/abstract_entity.js @@ -111,6 +111,8 @@ class AbstractEntity { /** * Mark the entity as (soft) deleted. It will be completely erased later. * + * This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead. + * * @param [deleteId=null] */ markAsDeleted(deleteId = null) { diff --git a/src/becca/entities/branch.js b/src/becca/entities/branch.js index 10ce46a54..8df3febb8 100644 --- a/src/becca/entities/branch.js +++ b/src/becca/entities/branch.js @@ -4,6 +4,10 @@ const Note = require('./note'); const AbstractEntity = require("./abstract_entity"); const sql = require("../../services/sql"); const dateUtils = require("../../services/date_utils"); +const utils = require("../../services/utils.js"); +const TaskContext = require("../../services/task_context.js"); +const cls = require("../../services/cls.js"); +const log = require("../../services/log.js"); /** * Branch represents a relationship between a child note and its parent note. Trilium allows a note to have multiple @@ -114,6 +118,63 @@ class Branch extends AbstractEntity { return !(this.branchId in this.becca.branches); } + /** + * Delete a branch. If this is a last note's branch, delete the note as well. + * + * @param {string} [deleteId] - optional delete identified + * @param {TaskContext} [taskContext] + * + * @return {boolean} - true if note has been deleted, false otherwise + */ + deleteBranch(deleteId, taskContext) { + if (!deleteId) { + deleteId = utils.randomString(10); + } + + if (!taskContext) { + taskContext = new TaskContext('no-progress-reporting'); + } + + taskContext.increaseProgressCount(); + + if (this.branchId === 'root' + || this.noteId === 'root' + || this.noteId === cls.getHoistedNoteId()) { + + throw new Error("Can't delete root or hoisted branch/note"); + } + + this.markAsDeleted(deleteId); + + const note = this.getNote(); + const notDeletedBranches = note.getParentBranches(); + + if (notDeletedBranches.length === 0) { + for (const childBranch of note.getChildBranches()) { + childBranch.deleteBranch(deleteId, taskContext); + } + + // first delete children and then parent - this will show up better in recent changes + + log.info("Deleting note " + note.noteId); + + for (const attribute of note.getOwnedAttributes()) { + attribute.markAsDeleted(deleteId); + } + + for (const relation of note.getTargetRelations()) { + relation.markAsDeleted(deleteId); + } + + note.markAsDeleted(deleteId); + + return true; + } + else { + return false; + } + } + beforeSaving() { if (this.notePosition === undefined || this.notePosition === null) { // TODO finding new position can be refactored into becca diff --git a/src/becca/entities/note.js b/src/becca/entities/note.js index 5f23e2d15..0192cff60 100644 --- a/src/becca/entities/note.js +++ b/src/becca/entities/note.js @@ -8,6 +8,7 @@ const dateUtils = require('../../services/date_utils'); const entityChangesService = require('../../services/entity_changes'); const AbstractEntity = require("./abstract_entity"); const NoteRevision = require("./note_revision"); +const TaskContext = require("../../services/task_context.js"); const LABEL = 'label'; const RELATION = 'relation'; @@ -1125,6 +1126,26 @@ class Note extends AbstractEntity { return cloningService.cloneNoteToBranch(this.noteId, branch.branchId); } + /** + * (Soft) delete a note and all its descendants. + * + * @param {string} [deleteId] - optional delete identified + * @param {TaskContext} [taskContext] + */ + deleteNote(deleteId, taskContext) { + if (!deleteId) { + deleteId = utils.randomString(10); + } + + if (!taskContext) { + taskContext = new TaskContext('no-progress-reporting'); + } + + for (const branch of this.getParentBranches()) { + branch.deleteBranch(deleteId, taskContext); + } + } + decrypt() { if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { try { diff --git a/src/etapi/branches.js b/src/etapi/branches.js index 71117e339..be6d9f516 100644 --- a/src/etapi/branches.js +++ b/src/etapi/branches.js @@ -71,7 +71,7 @@ function register(router) { return res.sendStatus(204); } - noteService.deleteBranch(branch, null, new TaskContext('no-progress-reporting')); + branch.deleteBranch(); res.sendStatus(204); }); diff --git a/src/etapi/notes.js b/src/etapi/notes.js index b35c296ff..788b0f4ff 100644 --- a/src/etapi/notes.js +++ b/src/etapi/notes.js @@ -98,7 +98,7 @@ function register(router) { return res.sendStatus(204); } - noteService.deleteNote(note, null, new TaskContext('no-progress-reporting')); + note.deleteNote(null, new TaskContext('no-progress-reporting')); res.sendStatus(204); }); diff --git a/src/routes/api/branches.js b/src/routes/api/branches.js index f05a6e0c3..3af5fe8ed 100644 --- a/src/routes/api/branches.js +++ b/src/routes/api/branches.js @@ -194,7 +194,7 @@ function deleteBranch(req) { const taskContext = TaskContext.getInstance(req.query.taskId, 'delete-notes'); const deleteId = utils.randomString(10); - const noteDeleted = noteService.deleteBranch(branch, deleteId, taskContext); + const noteDeleted = branch.deleteBranch(deleteId, taskContext); if (eraseNotes) { noteService.eraseNotesWithDeleteId(deleteId); diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index f602189e2..82dbb813a 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -73,7 +73,7 @@ function deleteNote(req) { const taskContext = TaskContext.getInstance(taskId, 'delete-notes'); - noteService.deleteNote(note, deleteId, taskContext); + note.deleteNote(deleteId, taskContext); if (eraseNotes) { noteService.eraseNotesWithDeleteId(deleteId); diff --git a/src/services/cloning.js b/src/services/cloning.js index 6fbd3adf1..d929a1cbf 100644 --- a/src/services/cloning.js +++ b/src/services/cloning.js @@ -89,8 +89,7 @@ function ensureNoteIsAbsentFromParent(noteId, parentNoteId) { throw new Error(`Cannot remove branch ${branch.branchId} between child ${noteId} and parent ${parentNoteId} because this would delete the note as well.`); } - const deleteId = utils.randomString(10); - noteService.deleteBranch(branch, deleteId, new TaskContext()); + branch.deleteBranch(); log.info(`Ensured note ${noteId} is NOT in parent note ${parentNoteId}`); } diff --git a/src/services/notes.js b/src/services/notes.js index 5609e0582..e69e603d4 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -17,6 +17,7 @@ const becca = require('../becca/becca'); const Branch = require('../becca/entities/branch'); const Note = require('../becca/entities/note'); const Attribute = require('../becca/entities/attribute'); +const TaskContext = require("./task_context.js"); function getNewNotePosition(parentNoteId) { const note = becca.notes[parentNoteId]; @@ -524,69 +525,6 @@ function updateNote(noteId, noteUpdates) { }; } -/** - * @param {Branch} branch - * @param {string|null} deleteId - * @param {TaskContext} taskContext - * - * @return {boolean} - true if note has been deleted, false otherwise - */ -function deleteBranch(branch, deleteId, taskContext) { - taskContext.increaseProgressCount(); - - if (!branch) { - return false; - } - - if (branch.branchId === 'root' - || branch.noteId === 'root' - || branch.noteId === cls.getHoistedNoteId()) { - - throw new Error("Can't delete root or hoisted branch/note"); - } - - branch.markAsDeleted(deleteId); - - const note = branch.getNote(); - const notDeletedBranches = note.getParentBranches(); - - if (notDeletedBranches.length === 0) { - for (const childBranch of note.getChildBranches()) { - deleteBranch(childBranch, deleteId, taskContext); - } - - // first delete children and then parent - this will show up better in recent changes - - log.info("Deleting note " + note.noteId); - - for (const attribute of note.getOwnedAttributes()) { - attribute.markAsDeleted(deleteId); - } - - for (const relation of note.getTargetRelations()) { - relation.markAsDeleted(deleteId); - } - - note.markAsDeleted(deleteId); - - return true; - } - else { - return false; - } -} - -/** - * @param {Note} note - * @param {string|null} deleteId - * @param {TaskContext} taskContext - */ -function deleteNote(note, deleteId, taskContext) { - for (const branch of note.getParentBranches()) { - deleteBranch(branch, deleteId, taskContext); - } -} - /** * @param {string} noteId * @param {TaskContext} taskContext @@ -938,8 +876,6 @@ module.exports = { createNewNote, createNewNoteWithTarget, updateNote, - deleteBranch, - deleteNote, undeleteNote, protectNoteRecursively, scanForLinks, From 3b58b83f8bb93c04263081f60d75f211320ed065 Mon Sep 17 00:00:00 2001 From: zadam Date: Tue, 19 Apr 2022 23:36:21 +0200 Subject: [PATCH 02/14] improved logging --- src/becca/becca_service.js | 4 ++-- src/becca/entities/note.js | 4 ++-- src/public/app/entities/note_short.js | 2 +- src/public/app/services/froca.js | 6 +++--- src/routes/api/clipper.js | 2 +- src/routes/api/notes.js | 10 +++++----- src/services/notes.js | 14 +++++++------- src/share/routes.js | 8 ++++---- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/becca/becca_service.js b/src/becca/becca_service.js index b27e985f5..f2cebbaae 100644 --- a/src/becca/becca_service.js +++ b/src/becca/becca_service.js @@ -67,7 +67,7 @@ function getNoteTitle(childNoteId, parentNoteId) { const parentNote = becca.notes[parentNoteId]; if (!childNote) { - log.info(`Cannot find note in cache for noteId ${childNoteId}`); + log.info(`Cannot find note in cache for noteId '${childNoteId}'`); return "[error fetching title]"; } @@ -162,7 +162,7 @@ function getNotePath(noteId) { const note = becca.notes[noteId]; if (!note) { - console.trace(`Cannot find note ${noteId} in cache.`); + console.trace(`Cannot find note '${noteId}' in cache.`); return; } diff --git a/src/becca/entities/note.js b/src/becca/entities/note.js index 0192cff60..595e41052 100644 --- a/src/becca/entities/note.js +++ b/src/becca/entities/note.js @@ -238,7 +238,7 @@ class Note extends AbstractEntity { setContent(content, ignoreMissingProtectedSession = false) { if (content === null || content === undefined) { - throw new Error(`Cannot set null content to note ${this.noteId}`); + throw new Error(`Cannot set null content to note '${this.noteId}'`); } if (this.isStringNote()) { @@ -260,7 +260,7 @@ class Note extends AbstractEntity { pojo.content = protectedSessionService.encrypt(pojo.content); } else if (!ignoreMissingProtectedSession) { - throw new Error(`Cannot update content of noteId=${this.noteId} since we're out of protected session.`); + throw new Error(`Cannot update content of noteId '${this.noteId}' since we're out of protected session.`); } } diff --git a/src/public/app/entities/note_short.js b/src/public/app/entities/note_short.js index 836755566..2c403080d 100644 --- a/src/public/app/entities/note_short.js +++ b/src/public/app/entities/note_short.js @@ -124,7 +124,7 @@ class NoteShort { return JSON.parse(content); } catch (e) { - console.log(`Cannot parse content of note ${this.noteId}: `, e.message); + console.log(`Cannot parse content of note '${this.noteId}': `, e.message); return null; } diff --git a/src/public/app/services/froca.js b/src/public/app/services/froca.js index abc59d7e0..91af4f1b2 100644 --- a/src/public/app/services/froca.js +++ b/src/public/app/services/froca.js @@ -179,7 +179,7 @@ class Froca { const searchResultNoteIds = await server.get('search-note/' + note.noteId); if (!Array.isArray(searchResultNoteIds)) { - throw new Error(`Search note ${note.noteId} failed: ${searchResultNoteIds}`); + throw new Error(`Search note '${note.noteId}' failed: ${searchResultNoteIds}`); } // reset all the virtual branches from old search results @@ -254,7 +254,7 @@ class Froca { return null; } else if (!noteId) { - console.trace(`Falsy noteId ${noteId}, returning null.`); + console.trace(`Falsy noteId '${noteId}', returning null.`); return null; } @@ -312,7 +312,7 @@ class Froca { if (!this.noteComplementPromises[noteId]) { this.noteComplementPromises[noteId] = server.get('notes/' + noteId) .then(row => new NoteComplement(row)) - .catch(e => console.error(`Cannot get note complement for note ${noteId}`)); + .catch(e => console.error(`Cannot get note complement for note '${noteId}'`)); // we don't want to keep large payloads forever in memory so we clean that up quite quickly // this cache is more meant to share the data between different components within one business transaction (e.g. loading of the note into the tab context and all the components) diff --git a/src/routes/api/clipper.js b/src/routes/api/clipper.js index d777b9bb9..b8bb158e7 100644 --- a/src/routes/api/clipper.js +++ b/src/routes/api/clipper.js @@ -138,7 +138,7 @@ function processContent(images, note, content) { value: imageNote.noteId }).save(); - log.info(`Replacing ${imageId} with ${url} in note ${note.noteId}`); + log.info(`Replacing '${imageId}' with '${url}' in note '${note.noteId}'`); rewrittenContent = utils.replaceAll(rewrittenContent, imageId, url); } diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index 82dbb813a..5f334edc8 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -96,7 +96,7 @@ function sortChildNotes(req) { const noteId = req.params.noteId; const {sortBy, sortDirection} = req.body; - log.info(`Sorting ${noteId} children with ${sortBy} ${sortDirection}`); + log.info(`Sorting '${noteId}' children with ${sortBy} ${sortDirection}`); const reverse = sortDirection === 'desc'; @@ -196,11 +196,11 @@ function changeTitle(req) { const note = becca.getNote(noteId); if (!note) { - return [404, `Note ${noteId} has not been found`]; + return [404, `Note '${noteId}' has not been found`]; } if (!note.isContentAvailable()) { - return [400, `Note ${noteId} is not available for change`]; + return [400, `Note '${noteId}' is not available for change`]; } const noteTitleChanged = note.title !== title; @@ -289,10 +289,10 @@ function uploadModifiedFile(req) { const note = becca.getNote(noteId); if (!note) { - return [404, `Note ${noteId} has not been found`]; + return [404, `Note '${noteId}' has not been found`]; } - log.info(`Updating note ${noteId} with content from ${filePath}`); + log.info(`Updating note '${noteId}' with content from ${filePath}`); noteRevisionService.createNoteRevision(note); diff --git a/src/services/notes.js b/src/services/notes.js index e69e603d4..86344061b 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -139,7 +139,7 @@ function createNewNote(params) { triggerNoteTitleChanged(note); triggerChildNoteCreated(note, parentNote); - log.info(`Created new note ${note.noteId}, branch ${branch.branchId} of type ${note.type}, mime ${note.mime}`); + log.info(`Created new note '${note.noteId}', branch '${branch.branchId}' of type '${note.type}', mime '${note.mime}'`); return { note, @@ -285,10 +285,10 @@ async function downloadImage(noteId, imageUrl) { imageUrlToNoteIdMapping[imageUrl] = note.noteId; - log.info(`Download of ${imageUrl} succeeded and was saved as image note ${note.noteId}`); + log.info(`Download of '${imageUrl}' succeeded and was saved as image note '${note.noteId}'`); } catch (e) { - log.error(`Download of ${imageUrl} for note ${noteId} failed with error: ${e.message} ${e.stack}`); + log.error(`Download of '${imageUrl}' for note '${noteId}' failed with error: ${e.message} ${e.stack}`); } } @@ -373,7 +373,7 @@ function downloadImages(noteId, content) { const origNote = becca.getNote(noteId); if (!origNote) { - log.error(`Cannot find note ${noteId} to replace image link.`); + log.error(`Cannot find note '${noteId}' to replace image link.`); return; } @@ -394,7 +394,7 @@ function downloadImages(noteId, content) { scanForLinks(origNote); - console.log(`Fixed the image links for note ${noteId} to the offline saved.`); + console.log(`Fixed the image links for note '${noteId}' to the offline saved.`); } }); }, 5000); @@ -491,7 +491,7 @@ function updateNote(noteId, noteUpdates) { const note = becca.getNote(noteId); if (!note.isContentAvailable()) { - throw new Error(`Note ${noteId} is not available for change!`); + throw new Error(`Note '${noteId}' is not available for change!`); } saveNoteRevision(note); @@ -533,7 +533,7 @@ function undeleteNote(noteId, taskContext) { const note = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); if (!note.isDeleted) { - log.error(`Note ${noteId} is not deleted and thus cannot be undeleted.`); + log.error(`Note '${noteId}' is not deleted and thus cannot be undeleted.`); return; } diff --git a/src/share/routes.js b/src/share/routes.js index 609028597..cb9a9c8e3 100644 --- a/src/share/routes.js +++ b/src/share/routes.js @@ -76,7 +76,7 @@ function register(router) { const note = shaca.getNote(noteId); if (!note) { - return res.status(404).send(`Note ${noteId} not found`); + return res.status(404).send(`Note '${noteId}' not found`); } addNoIndexHeader(note, res); @@ -89,7 +89,7 @@ function register(router) { const note = shaca.getNote(noteId); if (!note) { - return res.status(404).send(`Note ${noteId} not found`); + return res.status(404).send(`Note '${noteId}' not found`); } addNoIndexHeader(note, res); @@ -110,7 +110,7 @@ function register(router) { const image = shaca.getNote(req.params.noteId); if (!image) { - return res.status(404).send(`Note ${noteId} not found`); + return res.status(404).send(`Note '${req.params.noteId}' not found`); } else if (image.type !== 'image') { return res.status(400).send("Requested note is not an image"); @@ -129,7 +129,7 @@ function register(router) { const note = shaca.getNote(noteId); if (!note) { - return res.status(404).send(`Note ${noteId} not found`); + return res.status(404).send(`Note '${noteId}' not found`); } addNoIndexHeader(note, res); From 569c80f551090ff9e247641496e82d30063ecd64 Mon Sep 17 00:00:00 2001 From: zadam Date: Fri, 22 Apr 2022 00:07:59 +0200 Subject: [PATCH 03/14] release 0.51.1-beta --- package.json | 2 +- src/services/build.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3a0356b2d..602a7bef5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "trilium", "productName": "Trilium Notes", "description": "Trilium Notes", - "version": "0.51.0-beta", + "version": "0.51.1-beta", "license": "AGPL-3.0-only", "main": "electron.js", "bin": { diff --git a/src/services/build.js b/src/services/build.js index f5dc88301..4a8f65d21 100644 --- a/src/services/build.js +++ b/src/services/build.js @@ -1 +1 @@ -module.exports = { buildDate:"2022-04-10T14:13:51+02:00", buildRevision: "a04becc4ec653e21c2c80aa9d9ef5b7c9a8e1aa8" }; +module.exports = { buildDate:"2022-04-22T00:07:59+02:00", buildRevision: "3b58b83f8bb93c04263081f60d75f211320ed065" }; From 11578b1bc3dda7f29a91281ec28b5fe6f6c63fef Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 23 Apr 2022 23:06:42 +0200 Subject: [PATCH 04/14] fix "isActive()" detection to work well with splits, #2806 --- src/public/app/widgets/type_widgets/type_widget.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/public/app/widgets/type_widgets/type_widget.js b/src/public/app/widgets/type_widgets/type_widget.js index aece96078..49aa2212d 100644 --- a/src/public/app/widgets/type_widgets/type_widget.js +++ b/src/public/app/widgets/type_widgets/type_widget.js @@ -1,4 +1,5 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js"; +import appContext from "../../services/app_context.js"; export default class TypeWidget extends NoteContextAwareWidget { // for overriding @@ -34,7 +35,7 @@ export default class TypeWidget extends NoteContextAwareWidget { } isActive() { - return this.$widget.is(":visible"); + return this.$widget.is(":visible") && this.noteContext?.ntxId === appContext.tabManager.activeNtxId; } getContent() {} From dbd312c88db2b000ec0ce18c95bc8a27c0e621a1 Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 23 Apr 2022 23:07:08 +0200 Subject: [PATCH 05/14] addTextToEditor appends text to the end instead of the beginning --- src/public/app/widgets/type_widgets/editable_text.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/app/widgets/type_widgets/editable_text.js b/src/public/app/widgets/type_widgets/editable_text.js index e82ba6d68..d497ecbbe 100644 --- a/src/public/app/widgets/type_widgets/editable_text.js +++ b/src/public/app/widgets/type_widgets/editable_text.js @@ -191,7 +191,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { await this.initialized; this.textEditor.model.change(writer => { - const insertPosition = this.textEditor.model.document.selection.getFirstPosition(); + const insertPosition = this.textEditor.model.document.selection.getLastPosition(); writer.insertText(text, insertPosition); }); } From 0a45b587846336f1fea530cacd92c69502c67908 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 24 Apr 2022 13:23:01 +0200 Subject: [PATCH 06/14] fix doubling of icon tooltips, closes #2811 --- src/public/app/widgets/buttons/button_widget.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/public/app/widgets/buttons/button_widget.js b/src/public/app/widgets/buttons/button_widget.js index d925f2357..92f4d701d 100644 --- a/src/public/app/widgets/buttons/button_widget.js +++ b/src/public/app/widgets/buttons/button_widget.js @@ -71,7 +71,6 @@ export default class ButtonWidget extends NoteContextAwareWidget { } this.$widget - .attr("title", this.settings.title) .addClass(this.settings.icon); } From 70edd9a21009c6a1c041256be2bca8907544955c Mon Sep 17 00:00:00 2001 From: zadam Date: Fri, 29 Apr 2022 22:36:05 +0200 Subject: [PATCH 07/14] allow searching within mermaid diagrams, closes #2821 --- .../search/expressions/note_content_protected_fulltext.js | 2 +- .../search/expressions/note_content_unprotected_fulltext.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/search/expressions/note_content_protected_fulltext.js b/src/services/search/expressions/note_content_protected_fulltext.js index b24d3801a..c93017a9a 100644 --- a/src/services/search/expressions/note_content_protected_fulltext.js +++ b/src/services/search/expressions/note_content_protected_fulltext.js @@ -33,7 +33,7 @@ class NoteContentProtectedFulltextExp extends Expression { for (let {noteId, type, mime, content} of sql.iterateRows(` SELECT noteId, type, mime, content FROM notes JOIN note_contents USING (noteId) - WHERE type IN ('text', 'code') AND isDeleted = 0 AND isProtected = 1`)) { + WHERE type IN ('text', 'code', 'mermaid') AND isDeleted = 0 AND isProtected = 1`)) { if (!inputNoteSet.hasNoteId(noteId) || !(noteId in becca.notes)) { continue; diff --git a/src/services/search/expressions/note_content_unprotected_fulltext.js b/src/services/search/expressions/note_content_unprotected_fulltext.js index 60df92570..7b97722b8 100644 --- a/src/services/search/expressions/note_content_unprotected_fulltext.js +++ b/src/services/search/expressions/note_content_unprotected_fulltext.js @@ -27,7 +27,7 @@ class NoteContentUnprotectedFulltextExp extends Expression { for (let {noteId, type, mime, content} of sql.iterateRows(` SELECT noteId, type, mime, content FROM notes JOIN note_contents USING (noteId) - WHERE type IN ('text', 'code') AND isDeleted = 0 AND isProtected = 0`)) { + WHERE type IN ('text', 'code', 'mermaid') AND isDeleted = 0 AND isProtected = 0`)) { if (!inputNoteSet.hasNoteId(noteId) || !(noteId in becca.notes)) { continue; From f705c432fd1dcea973413655ba9a1cbc082eb01a Mon Sep 17 00:00:00 2001 From: zadam Date: Fri, 29 Apr 2022 22:59:00 +0200 Subject: [PATCH 08/14] allow combining tokens in text and title/attributes, fixes #2820 --- .../note_content_protected_fulltext.js | 15 +++++++++++++-- .../note_content_unprotected_fulltext.js | 15 +++++++++++++-- src/services/search/services/parse.js | 12 ++++++------ 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/services/search/expressions/note_content_protected_fulltext.js b/src/services/search/expressions/note_content_protected_fulltext.js index c93017a9a..05130e087 100644 --- a/src/services/search/expressions/note_content_protected_fulltext.js +++ b/src/services/search/expressions/note_content_protected_fulltext.js @@ -10,7 +10,7 @@ const utils = require("../../utils"); // FIXME: create common subclass with NoteContentUnprotectedFulltextExp to avoid duplication class NoteContentProtectedFulltextExp extends Expression { - constructor(operator, tokens, raw) { + constructor(operator, {tokens, raw, flatText}) { super(); if (operator !== '*=*') { @@ -19,6 +19,7 @@ class NoteContentProtectedFulltextExp extends Expression { this.tokens = tokens; this.raw = !!raw; + this.flatText = !!flatText; } execute(inputNoteSet) { @@ -49,7 +50,17 @@ class NoteContentProtectedFulltextExp extends Expression { content = this.preprocessContent(content, type, mime); - if (!this.tokens.find(token => !content.includes(token))) { + const nonMatchingToken = this.tokens.find(token => + !content.includes(token) && + ( + // in case of default fulltext search we should consider both title, attrs and content + // so e.g. "hello world" should match when "hello" is in title and "world" in content + !this.flatText + || !becca.notes[noteId].getFlatText().includes(token) + ) + ); + + if (!nonMatchingToken) { resultNoteSet.add(becca.notes[noteId]); } } diff --git a/src/services/search/expressions/note_content_unprotected_fulltext.js b/src/services/search/expressions/note_content_unprotected_fulltext.js index 7b97722b8..7abbd0d78 100644 --- a/src/services/search/expressions/note_content_unprotected_fulltext.js +++ b/src/services/search/expressions/note_content_unprotected_fulltext.js @@ -8,7 +8,7 @@ const utils = require("../../utils"); // FIXME: create common subclass with NoteContentProtectedFulltextExp to avoid duplication class NoteContentUnprotectedFulltextExp extends Expression { - constructor(operator, tokens, raw) { + constructor(operator, {tokens, raw, flatText}) { super(); if (operator !== '*=*') { @@ -17,6 +17,7 @@ class NoteContentUnprotectedFulltextExp extends Expression { this.tokens = tokens; this.raw = !!raw; + this.flatText = !!flatText; } execute(inputNoteSet) { @@ -35,7 +36,17 @@ class NoteContentUnprotectedFulltextExp extends Expression { content = this.preprocessContent(content, type, mime); - if (!this.tokens.find(token => !content.includes(token))) { + const nonMatchingToken = this.tokens.find(token => + !content.includes(token) && + ( + // in case of default fulltext search we should consider both title, attrs and content + // so e.g. "hello world" should match when "hello" is in title and "world" in content + !this.flatText + || !becca.notes[noteId].getFlatText().includes(token) + ) + ); + + if (!nonMatchingToken) { resultNoteSet.add(becca.notes[noteId]); } } diff --git a/src/services/search/services/parse.js b/src/services/search/services/parse.js index 02d2cc3bb..9ba5ce506 100644 --- a/src/services/search/services/parse.js +++ b/src/services/search/services/parse.js @@ -32,8 +32,8 @@ function getFulltext(tokens, searchContext) { if (!searchContext.fastSearch) { return new OrExp([ new NoteFlatTextExp(tokens), - new NoteContentProtectedFulltextExp('*=*', tokens), - new NoteContentUnprotectedFulltextExp('*=*', tokens) + new NoteContentProtectedFulltextExp('*=*', {tokens, flatText: true}), + new NoteContentUnprotectedFulltextExp('*=*', {tokens, flatText: true}) ]); } else { @@ -141,8 +141,8 @@ function getExpression(tokens, searchContext, level = 0) { i++; return new OrExp([ - new NoteContentUnprotectedFulltextExp(operator, [tokens[i].token], raw), - new NoteContentProtectedFulltextExp(operator, [tokens[i].token], raw) + new NoteContentUnprotectedFulltextExp(operator, {tokens: [tokens[i].token], raw }), + new NoteContentProtectedFulltextExp(operator, {tokens: [tokens[i].token], raw }) ]); } @@ -196,8 +196,8 @@ function getExpression(tokens, searchContext, level = 0) { return new OrExp([ new PropertyComparisonExp(searchContext, 'title', '*=*', tokens[i].token), - new NoteContentProtectedFulltextExp('*=*', [tokens[i].token]), - new NoteContentUnprotectedFulltextExp('*=*', [tokens[i].token]) + new NoteContentProtectedFulltextExp('*=*', {tokens: [tokens[i].token]}), + new NoteContentUnprotectedFulltextExp('*=*', {tokens: [tokens[i].token]}) ]); } From 6b61b0604ab2c085d2480c477476d3dff23168f5 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 1 May 2022 22:44:23 +0200 Subject: [PATCH 09/14] improve hiding of edit button #2787 --- src/public/app/widgets/buttons/edit_button.js | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/public/app/widgets/buttons/edit_button.js b/src/public/app/widgets/buttons/edit_button.js index af70cc6aa..9b458fcca 100644 --- a/src/public/app/widgets/buttons/edit_button.js +++ b/src/public/app/widgets/buttons/edit_button.js @@ -1,5 +1,7 @@ import ButtonWidget from "./button_widget.js"; import appContext from "../../services/app_context.js"; +import attributeService from "../../services/attributes.js"; +import protectedSessionHolder from "../../services/protected_session_holder.js"; export default class EditButton extends ButtonWidget { isEnabled() { @@ -22,9 +24,29 @@ export default class EditButton extends ButtonWidget { } async refreshWithNote(note) { - // can't do this in isEnabled() since isReadOnly is async - this.toggleInt(await this.noteContext.isReadOnly()); + if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) { + this.toggleInt(false); + } + else { + // prevent flickering by assuming hidden before async operation + this.toggleInt(false); + + // can't do this in isEnabled() since isReadOnly is async + this.toggleInt(await this.noteContext.isReadOnly()); + } await super.refreshWithNote(note); } + + entitiesReloadedEvent({loadResults}) { + if (loadResults.getAttributes().find( + attr => attr.type === 'label' + && attr.name.toLowerCase().includes("readonly") + && attributeService.isAffecting(attr, this.note) + )) { + this.noteContext.readOnlyTemporarilyDisabled = false; + + this.refresh(); + } + } } From f9c01851ef40982d205415492da6e054ff4e61d3 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 1 May 2022 22:47:26 +0200 Subject: [PATCH 10/14] fix missing closing div tag in word count demo widget, closes #2829 --- db/demo.zip | Bin 848773 -> 848819 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/db/demo.zip b/db/demo.zip index 7f106b5686d86fb32b0ff774e42ef485c8273f05..a2327ff331b51bc6bece343c3f61a63a1ec7fc3d 100644 GIT binary patch delta 32085 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 z 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 a=Vxw016M2@?gjj>-3C5dxB_pqasLPRCn9VB delta 31633 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

SE=()SeN!Lv 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 t+&{nHf?M2mI25r6S Date: Sun, 1 May 2022 23:16:47 +0200 Subject: [PATCH 11/14] make sure shaca is loaded before any request --- src/share/routes.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/share/routes.js b/src/share/routes.js index cb9a9c8e3..917e66d14 100644 --- a/src/share/routes.js +++ b/src/share/routes.js @@ -62,16 +62,18 @@ function register(router) { }); router.get('/share/:shareId', (req, res, next) => { - const {shareId} = req.params; - shacaLoader.ensureLoad(); + const {shareId} = req.params; + const note = shaca.aliasToNote[shareId] || shaca.notes[shareId]; renderNote(note, res); }); router.get('/share/api/notes/:noteId', (req, res, next) => { + shacaLoader.ensureLoad(); + const {noteId} = req.params; const note = shaca.getNote(noteId); @@ -85,6 +87,8 @@ function register(router) { }); router.get('/share/api/notes/:noteId/download', (req, res, next) => { + shacaLoader.ensureLoad(); + const {noteId} = req.params; const note = shaca.getNote(noteId); @@ -107,6 +111,8 @@ function register(router) { }); router.get('/share/api/images/:noteId/:filename', (req, res, next) => { + shacaLoader.ensureLoad(); + const image = shaca.getNote(req.params.noteId); if (!image) { @@ -118,13 +124,15 @@ function register(router) { addNoIndexHeader(image, res); - res.set('Content-Type', image.mime); + res.setHeader('Content-Type', image.mime); res.send(image.getContent()); }); // used for PDF viewing router.get('/share/api/notes/:noteId/view', (req, res, next) => { + shacaLoader.ensureLoad(); + const {noteId} = req.params; const note = shaca.getNote(noteId); From 26e1ff4e16aeed213994732a778f7116ae635156 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 1 May 2022 23:18:35 +0200 Subject: [PATCH 12/14] release 0.51.2 --- package.json | 2 +- src/services/build.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 602a7bef5..d2a291edd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "trilium", "productName": "Trilium Notes", "description": "Trilium Notes", - "version": "0.51.1-beta", + "version": "0.51.2", "license": "AGPL-3.0-only", "main": "electron.js", "bin": { diff --git a/src/services/build.js b/src/services/build.js index 4a8f65d21..6717d2293 100644 --- a/src/services/build.js +++ b/src/services/build.js @@ -1 +1 @@ -module.exports = { buildDate:"2022-04-22T00:07:59+02:00", buildRevision: "3b58b83f8bb93c04263081f60d75f211320ed065" }; +module.exports = { buildDate:"2022-05-01T23:18:35+02:00", buildRevision: "b3763eed610fa3f2aabbcbdbd21efca704a5dd08" }; From a9dc62505d9c3aff26aca259785e389eeebceec7 Mon Sep 17 00:00:00 2001 From: zadam Date: Mon, 2 May 2022 21:23:40 +0200 Subject: [PATCH 13/14] fix height of textarea of SQL console --- src/public/app/widgets/note_detail.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/public/app/widgets/note_detail.js b/src/public/app/widgets/note_detail.js index dea666515..a54fc4250 100644 --- a/src/public/app/widgets/note_detail.js +++ b/src/public/app/widgets/note_detail.js @@ -149,7 +149,8 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { // https://github.com/zadam/trilium/issues/2522 this.$widget.toggleClass("full-height", !this.noteContext.hasNoteList() - && ['editable-text', 'editable-code'].includes(this.type)); + && ['editable-text', 'editable-code'].includes(this.type) + && this.mime !== 'text/x-sqlite;schema=trilium'); } getTypeWidget() { From 5dab189815f2447a8d29d071539ee7906102fd5f Mon Sep 17 00:00:00 2001 From: zadam Date: Tue, 3 May 2022 00:30:09 +0200 Subject: [PATCH 14/14] recovery for tree cycle errors, #2831 --- src/services/consistency_checks.js | 57 +++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/src/services/consistency_checks.js b/src/services/consistency_checks.js index 267c9fec3..dbf4b4478 100644 --- a/src/services/consistency_checks.js +++ b/src/services/consistency_checks.js @@ -56,41 +56,66 @@ class ConsistencyChecks { childToParents[childNoteId].push(parentNoteId); } + /** @returns {boolean} true if cycle was found and we should try again */ const checkTreeCycle = (noteId, path) => { if (noteId === 'root') { - return; - } - - if (!childToParents[noteId] || childToParents[noteId].length === 0) { - logError(`No parents found for note ${noteId}`); - - this.unrecoveredConsistencyErrors = true; - return; + return false; } for (const parentNoteId of childToParents[noteId]) { if (path.includes(parentNoteId)) { - logError(`Tree cycle detected at parent-child relationship: ${parentNoteId} - ${noteId}, whole path: ${path}`); + if (this.autoFix) { + const branch = becca.getBranchFromChildAndParent(noteId, parentNoteId); + branch.markAsDeleted('cycle-autofix'); + logFix(`Branch '${branch.branchId}' between child '${noteId}' and parent '${parentNoteId}' has been deleted since it was causing a tree cycle.`); - this.unrecoveredConsistencyErrors = true; + return true; + } + else { + logError(`Tree cycle detected at parent-child relationship: ${parentNoteId} - ${noteId}, whole path: ${path}`); + + this.unrecoveredConsistencyErrors = true; + } } else { const newPath = path.slice(); newPath.push(noteId); - checkTreeCycle(parentNoteId, newPath); + const retryNeeded = checkTreeCycle(parentNoteId, newPath); + + if (retryNeeded) { + return true; + } } } + + return false; }; const noteIds = Object.keys(childToParents); for (const noteId of noteIds) { - checkTreeCycle(noteId, []); + const retryNeeded = checkTreeCycle(noteId, []); + + if (retryNeeded) { + return true; + } } - if (childToParents['root'].length !== 1 || childToParents['root'][0] !== 'none') { - logError('Incorrect root parent: ' + JSON.stringify(childToParents['root'])); - this.unrecoveredConsistencyErrors = true; + return false; + } + + checkAndRepairTreeCycles() { + let treeFixed = false; + + while (this.checkTreeCycles()) { + // fixing cycle means deleting branches, we might need to create a new branch to recover the note + this.findExistencyIssues(); + + treeFixed = true; + } + + if (treeFixed) { + this.reloadNeeded = true; } } @@ -646,7 +671,7 @@ class ConsistencyChecks { if (!this.unrecoveredConsistencyErrors) { // we run this only if basic checks passed since this assumes basic data consistency - this.checkTreeCycles(); + this.checkAndRepairTreeCycles(); } if (this.reloadNeeded) {