Compare commits

...

32 Commits

Author SHA1 Message Date
zadam
515c5411a6 release 0.61.5-beta 2023-08-16 23:02:15 +02:00
zadam
3f7a5504c7 prettier config 2023-08-16 20:31:14 +02:00
zadam
8c7c37cf98 updates 2023-08-16 20:12:22 +02:00
zadam
c0f48c0e99 mermaid 10.3.1 2023-08-15 10:30:30 +02:00
zadam
abedf2bba4 fix pasting into ckeditor, upgrade to v39.0.1 2023-08-15 10:25:23 +02:00
zadam
bb0137b2fd fix displaying options / book on firefox, closes #4165 2023-08-15 10:25:23 +02:00
zadam
6c54c7d17d save enex images as attachment, fixes #4163 2023-08-15 10:25:23 +02:00
zadam
90255ac55b release 0.61.4-beta 2023-08-10 23:49:37 +02:00
zadam
e741c2826c release 0.61.4-beta 2023-08-10 23:48:27 +02:00
zadam
026992db78 release 0.61.4-beta 2023-08-10 23:46:42 +02:00
zadam
33780c1e17 fix note tooltip code rendering 2023-08-10 13:50:25 +02:00
zadam
ede9c43f67 API docs 2023-08-10 13:40:26 +02:00
zadam
5c12ac4eee fix CKEditor crashing 2023-08-10 13:40:15 +02:00
zadam
522518cf0d disambiguous query 2023-08-09 23:12:31 +02:00
zadam
1d869d25c2 fix #clipperInbox, closes #4153 2023-08-09 23:00:42 +02:00
zadam
a9cdd93cb4 added options to disable auto-opening of promoted attributes and edited notes ribbon tabs, closes #4151 2023-08-09 22:50:41 +02:00
zadam
4240da349d add shared info to mobile layout, closes #4147 2023-08-09 00:02:45 +02:00
zadam
c257bc07a8 clipper now creates notes with image attachments instead of image notes 2023-08-09 00:01:31 +02:00
zadam
00eaa16985 clipper now creates notes with image attachments instead of image notes 2023-08-08 23:42:47 +02:00
zadam
fefb059564 clipper now creates notes with image attachments instead of image notes 2023-08-08 23:07:59 +02:00
zadam
9166765ced fix include note sizing when in readonly mode, closes #4135 2023-08-08 22:56:45 +02:00
zadam
6ae7661603 note path validation 2023-08-02 23:23:31 +02:00
zadam
30e75056bd release 0.61.3-beta 2023-07-31 23:03:45 +02:00
zadam
530e56dcb5 fixed comment 2023-07-31 22:59:47 +02:00
zadam
63675bfbae make migration 223 NOOP 2023-07-30 21:54:01 +02:00
zadam
696ce38083 ckeditor 38.1.1 2023-07-30 00:32:16 +02:00
zadam
12014b9f4d sync fixes and refactorings 2023-07-29 23:35:08 +02:00
zadam
e8b52f9e6c sync fixes and refactorings 2023-07-29 23:25:02 +02:00
zadam
04b125afc0 sync fixes and refactorings 2023-07-29 21:59:20 +02:00
zadam
2a7fe85020 VACUUM database after migration 2023-07-28 16:22:10 +02:00
zadam
119050e355 fix migration 2023-07-28 16:13:31 +02:00
zadam
72122d0f95 fix blobIds - they shouldn't contain + and / characters 2023-07-28 15:55:26 +02:00
60 changed files with 2087 additions and 1829 deletions

View File

@@ -1,11 +1,13 @@
//https://prettier.io/docs/en/options.html
//https://prettier.io/docs/en/options.html
module.exports = {
semi: true,
trailingComma: 'es5',
trailingComma: 'none',
singleQuote: true,
printWidth: 120,
printWidth: 100,
tabWidth: 4,
// useTabs: false,
// bracketSpacing: true,
useTabs: false,
quoteProps: "as-needed",
bracketSpacing: true,
arrowParens: "avoid"
// htmlWhitespaceSensitivity: 'ignore',
};

View File

@@ -0,0 +1 @@
SELECT 1;

View File

@@ -1,3 +0,0 @@
CREATE INDEX IDX_notes_blobId on notes (blobId);
CREATE INDEX IDX_revisions_blobId on revisions (blobId);
CREATE INDEX IDX_attachments_blobId on attachments (blobId);

View File

@@ -0,0 +1,14 @@
UPDATE blobs SET blobId = REPLACE(blobId, '+', 'X');
UPDATE blobs SET blobId = REPLACE(blobId, '/', 'Y');
UPDATE notes SET blobId = REPLACE(blobId, '+', 'X');
UPDATE notes SET blobId = REPLACE(blobId, '/', 'Y');
UPDATE attachments SET blobId = REPLACE(blobId, '+', 'X');
UPDATE attachments SET blobId = REPLACE(blobId, '/', 'Y');
UPDATE revisions SET blobId = REPLACE(blobId, '+', 'X');
UPDATE revisions SET blobId = REPLACE(blobId, '/', 'Y');
UPDATE entity_changes SET entityId = REPLACE(entityId, '+', 'X') WHERE entityName = 'blobs';
UPDATE entity_changes SET entityId = REPLACE(entityId, '/', 'Y') WHERE entityName = 'blobs';

View File

@@ -0,0 +1,3 @@
CREATE INDEX IF NOT EXISTS IDX_notes_blobId on notes (blobId);
CREATE INDEX IF NOT EXISTS IDX_revisions_blobId on revisions (blobId);
CREATE INDEX IF NOT EXISTS IDX_attachments_blobId on attachments (blobId);

View File

@@ -259,7 +259,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line244">line 244</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line246">line 246</a>
</li></ul></dd>
@@ -394,90 +394,6 @@
<h4 class="name" id="addEntityChange"><span class="type-signature">(protected) </span>addEntityChange<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>
</dl>
@@ -1022,7 +938,91 @@ This is a low-level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line261">line 261</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line263">line 263</a>
</li></ul></dd>
</dl>
<h4 class="name" id="putEntityChange"><span class="type-signature">(protected) </span>putEntityChange<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>

View File

@@ -825,7 +825,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line244">line 244</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line246">line 246</a>
</li></ul></dd>
@@ -965,95 +965,6 @@ and relation (representing named relationship between source and target note)</d
<h4 class="name" id="addEntityChange"><span class="type-signature">(protected) </span>addEntityChange<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#addEntityChange">AbstractBeccaEntity#addEntityChange</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>
</dl>
@@ -1940,7 +1851,96 @@ This is a low-level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line261">line 261</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line263">line 263</a>
</li></ul></dd>
</dl>
<h4 class="name" id="putEntityChange"><span class="type-signature">(protected) </span>putEntityChange<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#putEntityChange">AbstractBeccaEntity#putEntityChange</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>

View File

@@ -945,7 +945,7 @@ of deletion should not act as a clone.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line244">line 244</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line246">line 246</a>
</li></ul></dd>
@@ -1085,95 +1085,6 @@ of deletion should not act as a clone.
<h4 class="name" id="addEntityChange"><span class="type-signature">(protected) </span>addEntityChange<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#addEntityChange">AbstractBeccaEntity#addEntityChange</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>
</dl>
@@ -2054,7 +1965,96 @@ This is a low-level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line261">line 261</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line263">line 263</a>
</li></ul></dd>
</dl>
<h4 class="name" id="putEntityChange"><span class="type-signature">(protected) </span>putEntityChange<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#putEntityChange">AbstractBeccaEntity#putEntityChange</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>

View File

@@ -694,7 +694,7 @@ from tokenHash and token.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line244">line 244</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line246">line 246</a>
</li></ul></dd>
@@ -834,95 +834,6 @@ from tokenHash and token.</div>
<h4 class="name" id="addEntityChange"><span class="type-signature">(protected) </span>addEntityChange<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#addEntityChange">AbstractBeccaEntity#addEntityChange</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>
</dl>
@@ -1497,7 +1408,96 @@ This is a low-level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line261">line 261</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line263">line 263</a>
</li></ul></dd>
</dl>
<h4 class="name" id="putEntityChange"><span class="type-signature">(protected) </span>putEntityChange<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#putEntityChange">AbstractBeccaEntity#putEntityChange</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>

View File

@@ -1171,7 +1171,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line244">line 244</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line246">line 246</a>
</li></ul></dd>
@@ -1636,95 +1636,6 @@ See addLabel, addRelation for more specific methods.
<h4 class="name" id="addEntityChange"><span class="type-signature">(protected) </span>addEntityChange<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#addEntityChange">AbstractBeccaEntity#addEntityChange</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>
</dl>
@@ -12325,7 +12236,96 @@ This is a low-level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line261">line 261</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line263">line 263</a>
</li></ul></dd>
</dl>
<h4 class="name" id="putEntityChange"><span class="type-signature">(protected) </span>putEntityChange<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#putEntityChange">AbstractBeccaEntity#putEntityChange</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>

View File

@@ -552,7 +552,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line244">line 244</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line246">line 246</a>
</li></ul></dd>
@@ -692,95 +692,6 @@
<h4 class="name" id="addEntityChange"><span class="type-signature">(protected) </span>addEntityChange<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#addEntityChange">AbstractBeccaEntity#addEntityChange</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>
</dl>
@@ -1355,7 +1266,96 @@ This is a low-level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line261">line 261</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line263">line 263</a>
</li></ul></dd>
</dl>
<h4 class="name" id="putEntityChange"><span class="type-signature">(protected) </span>putEntityChange<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#putEntityChange">AbstractBeccaEntity#putEntityChange</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>

View File

@@ -484,7 +484,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line244">line 244</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line246">line 246</a>
</li></ul></dd>
@@ -624,95 +624,6 @@
<h4 class="name" id="addEntityChange"><span class="type-signature">(protected) </span>addEntityChange<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#addEntityChange">AbstractBeccaEntity#addEntityChange</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>
</dl>
@@ -1287,7 +1198,96 @@ This is a low-level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line261">line 261</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line263">line 263</a>
</li></ul></dd>
</dl>
<h4 class="name" id="putEntityChange"><span class="type-signature">(protected) </span>putEntityChange<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#putEntityChange">AbstractBeccaEntity#putEntityChange</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>

View File

@@ -89,8 +89,8 @@ class AbstractBeccaEntity {
}
/** @protected */
addEntityChange(isDeleted = false) {
entityChangesService.addEntityChange({
putEntityChange(isDeleted = false) {
entityChangesService.putEntityChange({
entityName: this.constructor.entityName,
entityId: this[this.constructor.primaryKeyName],
hash: this.generateHash(isDeleted),
@@ -129,7 +129,7 @@ class AbstractBeccaEntity {
return;
}
this.addEntityChange(false);
this.putEntityChange(false);
if (!cls.isEntityEventsDisabled()) {
const eventPayload = {
@@ -203,6 +203,8 @@ class AbstractBeccaEntity {
}
sql.execute("DELETE FROM blobs WHERE blobId = ?", [oldBlobId]);
// blobs are not marked as erased in entity_changes, they are just purged completely
// this is because technically every keystroke can create a new blob and there would be just too many
sql.execute("DELETE FROM entity_changes WHERE entityName = 'blobs' AND entityId = ?", [oldBlobId]);
}
@@ -245,7 +247,7 @@ class AbstractBeccaEntity {
// access to the decrypted content
const hash = blobService.calculateContentHash(pojo);
entityChangesService.addEntityChange({
entityChangesService.putEntityChange({
entityName: 'blobs',
entityId: newBlobId,
hash: hash,
@@ -305,7 +307,7 @@ class AbstractBeccaEntity {
log.info(`Marking ${entityName} ${entityId} as deleted`);
this.addEntityChange(true);
this.putEntityChange(true);
eventService.emit(eventService.ENTITY_DELETED, { entityName, entityId, entity: this });
}
@@ -322,7 +324,7 @@ class AbstractBeccaEntity {
log.info(`Marking ${entityName} ${entityId} as deleted`);
this.addEntityChange(true);
this.putEntityChange(true);
eventService.emit(eventService.ENTITY_DELETED, { entityName, entityId, entity: this });
}

View File

@@ -5,8 +5,8 @@
}
/*
* CKEditor 5 (v38.0.1) content styles.
* Generated on Thu, 25 May 2023 13:25:51 GMT.
* CKEditor 5 (v39.0.1) content styles.
* Generated on Thu, 10 Aug 2023 09:21:07 GMT.
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/content-styles.html
*/
@@ -15,8 +15,8 @@
--ck-color-image-caption-text: hsl(0, 0%, 20%);
--ck-color-mention-background: hsla(341, 100%, 30%, 0.1);
--ck-color-mention-text: hsl(341, 100%, 30%);
--ck-color-table-caption-background: hsl(0, 0%, 97%);
--ck-color-table-caption-text: hsl(0, 0%, 20%);
--ck-color-selector-caption-background: hsl(0, 0%, 97%);
--ck-color-selector-caption-text: hsl(0, 0%, 20%);
--ck-highlight-marker-blue: hsl(201, 97%, 72%);
--ck-highlight-marker-green: hsl(120, 93%, 68%);
--ck-highlight-marker-pink: hsl(345, 96%, 73%);
@@ -28,227 +28,107 @@
--ck-todo-list-checkmark-size: 16px;
}
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
.ck-content blockquote {
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table .ck-table-resized {
table-layout: fixed;
}
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table table {
overflow: hidden;
padding-right: 1.5em;
padding-left: 1.5em;
margin-left: 0;
margin-right: 0;
font-style: italic;
border-left: solid 5px hsl(0, 0%, 80%);
}
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
.ck-content[dir="rtl"] blockquote {
border-left: 0;
border-right: solid 5px hsl(0, 0%, 80%);
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table td,
.ck-content .table th {
overflow-wrap: break-word;
position: relative;
}
/* @ckeditor/ckeditor5-basic-styles/theme/code.css */
.ck-content code {
background-color: hsla(0, 0%, 78%, 0.3);
padding: .15em;
border-radius: 2px;
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table {
margin: 0.9em auto;
display: table;
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-tiny {
font-size: .7em;
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
height: 100%;
border: 1px double hsl(0, 0%, 70%);
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-small {
font-size: .85em;
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table table td,
.ck-content .table table th {
min-width: 2em;
padding: .4em;
border: 1px solid hsl(0, 0%, 75%);
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-big {
font-size: 1.4em;
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table table th {
font-weight: bold;
background: hsla(0, 0%, 0%, 5%);
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-huge {
font-size: 1.8em;
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content[dir="rtl"] .table th {
text-align: right;
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-yellow {
background-color: var(--ck-highlight-marker-yellow);
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content[dir="ltr"] .table th {
text-align: left;
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-green {
background-color: var(--ck-highlight-marker-green);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-pink {
background-color: var(--ck-highlight-marker-pink);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-blue {
background-color: var(--ck-highlight-marker-blue);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .pen-red {
color: var(--ck-highlight-pen-red);
background-color: transparent;
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .pen-green {
color: var(--ck-highlight-pen-green);
background-color: transparent;
}
/* @ckeditor/ckeditor5-image/theme/imagecaption.css */
.ck-content .image > figcaption {
/* @ckeditor/ckeditor5-table/theme/tablecaption.css */
.ck-content .table > figcaption {
display: table-caption;
caption-side: bottom;
caption-side: top;
word-break: break-word;
color: var(--ck-color-image-caption-text);
background-color: var(--ck-color-image-caption-background);
text-align: center;
color: var(--ck-color-selector-caption-text);
background-color: var(--ck-color-selector-caption-background);
padding: .6em;
font-size: .75em;
outline-offset: -1px;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image {
display: table;
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break {
position: relative;
clear: both;
text-align: center;
margin: 0.9em auto;
min-width: 50px;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image img {
display: block;
margin: 0 auto;
max-width: 100%;
min-width: 100%;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image-inline {
/*
* Normally, the .image-inline would have "display: inline-block" and "img { width: 100% }" (to follow the wrapper while resizing).;
* Unfortunately, together with "srcset", it gets automatically stretched up to the width of the editing root.
* This strange behavior does not happen with inline-flex.
*/
display: inline-flex;
max-width: 100%;
align-items: flex-start;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image-inline picture {
padding: 5px 0;
display: flex;
align-items: center;
justify-content: center;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image-inline picture,
.ck-content .image-inline img {
flex-grow: 1;
flex-shrink: 1;
max-width: 100%;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized {
max-width: 100%;
display: block;
box-sizing: border-box;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized img {
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break::after {
content: '';
position: absolute;
border-bottom: 2px dashed hsl(0, 0%, 77%);
width: 100%;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized > figcaption {
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break__label {
position: relative;
z-index: 1;
padding: .3em .6em;
display: block;
text-transform: uppercase;
border: 1px solid hsl(0, 0%, 77%);
border-radius: 2px;
font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
font-size: 0.75em;
font-weight: bold;
color: hsl(0, 0%, 20%);
background: hsl(0, 0%, 100%);
box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-block-align-left,
.ck-content .image-style-block-align-right {
max-width: calc(100% - var(--ck-image-style-spacing));
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-left,
.ck-content .image-style-align-right {
clear: none;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-side {
float: right;
margin-left: var(--ck-image-style-spacing);
max-width: 50%;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-left {
float: left;
margin-right: var(--ck-image-style-spacing);
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-center {
margin-left: auto;
margin-right: auto;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-right {
float: right;
margin-left: var(--ck-image-style-spacing);
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-block-align-right {
margin-right: 0;
margin-left: auto;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-block-align-left {
margin-left: 0;
margin-right: auto;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content p + .image-style-align-left,
.ck-content p + .image-style-align-right,
.ck-content p + .image-style-side {
margin-top: 0;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-inline.image-style-align-left,
.ck-content .image-inline.image-style-align-right {
margin-top: var(--ck-inline-image-style-spacing);
margin-bottom: var(--ck-inline-image-style-spacing);
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-inline.image-style-align-left {
margin-right: var(--ck-inline-image-style-spacing);
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-inline.image-style-align-right {
margin-left: var(--ck-inline-image-style-spacing);
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol {
list-style-type: decimal;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol {
list-style-type: lower-latin;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol ol {
list-style-type: lower-roman;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol ol ol {
list-style-type: upper-latin;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol ol ol ol {
list-style-type: upper-roman;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul {
list-style-type: disc;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul ul {
list-style-type: circle;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul ul ul {
list-style-type: square;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul ul ul ul {
list-style-type: square;
/* @ckeditor/ckeditor5-media-embed/theme/mediaembed.css */
.ck-content .media {
clear: both;
margin: 0.9em 0;
display: block;
min-width: 15em;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list {
@@ -317,107 +197,240 @@
.ck-content .todo-list .todo-list__label .todo-list__label__description {
vertical-align: middle;
}
/* @ckeditor/ckeditor5-media-embed/theme/mediaembed.css */
.ck-content .media {
clear: both;
margin: 0.9em 0;
display: block;
min-width: 15em;
}
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break {
position: relative;
clear: both;
padding: 5px 0;
display: flex;
align-items: center;
justify-content: center;
}
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break::after {
content: '';
position: absolute;
border-bottom: 2px dashed hsl(0, 0%, 77%);
width: 100%;
}
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break__label {
position: relative;
z-index: 1;
padding: .3em .6em;
display: block;
text-transform: uppercase;
border: 1px solid hsl(0, 0%, 77%);
border-radius: 2px;
font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
font-size: 0.75em;
font-weight: bold;
color: hsl(0, 0%, 20%);
background: hsl(0, 0%, 100%);
box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table .ck-table-resized {
table-layout: fixed;
}
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table table {
overflow: hidden;
}
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table td,
.ck-content .table th {
position: relative;
}
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table {
margin: 0.9em auto;
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image {
display: table;
}
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
height: 100%;
border: 1px double hsl(0, 0%, 70%);
}
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table table td,
.ck-content .table table th {
min-width: 2em;
padding: .4em;
border: 1px solid hsl(0, 0%, 75%);
}
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table table th {
font-weight: bold;
background: hsla(0, 0%, 0%, 5%);
}
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content[dir="rtl"] .table th {
text-align: right;
}
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content[dir="ltr"] .table th {
text-align: left;
}
/* @ckeditor/ckeditor5-table/theme/tablecaption.css */
.ck-content .table > figcaption {
display: table-caption;
caption-side: top;
word-break: break-word;
clear: both;
text-align: center;
color: var(--ck-color-table-caption-text);
background-color: var(--ck-color-table-caption-background);
margin: 0.9em auto;
min-width: 50px;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image img {
display: block;
margin: 0 auto;
max-width: 100%;
min-width: 100%;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image-inline {
/*
* Normally, the .image-inline would have "display: inline-block" and "img { width: 100% }" (to follow the wrapper while resizing).;
* Unfortunately, together with "srcset", it gets automatically stretched up to the width of the editing root.
* This strange behavior does not happen with inline-flex.
*/
display: inline-flex;
max-width: 100%;
align-items: flex-start;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image-inline picture {
display: flex;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image-inline picture,
.ck-content .image-inline img {
flex-grow: 1;
flex-shrink: 1;
max-width: 100%;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized {
max-width: 100%;
display: block;
box-sizing: border-box;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized img {
width: 100%;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized > figcaption {
display: block;
}
/* @ckeditor/ckeditor5-image/theme/imagecaption.css */
.ck-content .image > figcaption {
display: table-caption;
caption-side: bottom;
word-break: break-word;
color: var(--ck-color-image-caption-text);
background-color: var(--ck-color-image-caption-background);
padding: .6em;
font-size: .75em;
outline-offset: -1px;
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-yellow {
background-color: var(--ck-highlight-marker-yellow);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-green {
background-color: var(--ck-highlight-marker-green);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-pink {
background-color: var(--ck-highlight-marker-pink);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-blue {
background-color: var(--ck-highlight-marker-blue);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .pen-red {
color: var(--ck-highlight-pen-red);
background-color: transparent;
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .pen-green {
color: var(--ck-highlight-pen-green);
background-color: transparent;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol {
list-style-type: decimal;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol {
list-style-type: lower-latin;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol ol {
list-style-type: lower-roman;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol ol ol {
list-style-type: upper-latin;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol ol ol ol {
list-style-type: upper-roman;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul {
list-style-type: disc;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul ul {
list-style-type: circle;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul ul ul {
list-style-type: square;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul ul ul ul {
list-style-type: square;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-block-align-left,
.ck-content .image-style-block-align-right {
max-width: calc(100% - var(--ck-image-style-spacing));
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-left,
.ck-content .image-style-align-right {
clear: none;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-side {
float: right;
margin-left: var(--ck-image-style-spacing);
max-width: 50%;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-left {
float: left;
margin-right: var(--ck-image-style-spacing);
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-center {
margin-left: auto;
margin-right: auto;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-right {
float: right;
margin-left: var(--ck-image-style-spacing);
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-block-align-right {
margin-right: 0;
margin-left: auto;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-block-align-left {
margin-left: 0;
margin-right: auto;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content p + .image-style-align-left,
.ck-content p + .image-style-align-right,
.ck-content p + .image-style-side {
margin-top: 0;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-inline.image-style-align-left,
.ck-content .image-inline.image-style-align-right {
margin-top: var(--ck-inline-image-style-spacing);
margin-bottom: var(--ck-inline-image-style-spacing);
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-inline.image-style-align-left {
margin-right: var(--ck-inline-image-style-spacing);
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-inline.image-style-align-right {
margin-left: var(--ck-inline-image-style-spacing);
}
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
.ck-content blockquote {
overflow: hidden;
padding-right: 1.5em;
padding-left: 1.5em;
margin-left: 0;
margin-right: 0;
font-style: italic;
border-left: solid 5px hsl(0, 0%, 80%);
}
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
.ck-content[dir="rtl"] blockquote {
border-left: 0;
border-right: solid 5px hsl(0, 0%, 80%);
}
/* @ckeditor/ckeditor5-basic-styles/theme/code.css */
.ck-content code {
background-color: hsla(0, 0%, 78%, 0.3);
padding: .15em;
border-radius: 2px;
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-tiny {
font-size: .7em;
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-small {
font-size: .85em;
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-big {
font-size: 1.4em;
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-huge {
font-size: 1.8em;
}
/* @ckeditor/ckeditor5-mention/theme/mention.css */
.ck-content .mention {
background: var(--ck-color-mention-background);
color: var(--ck-color-mention-text);
}
/* @ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css */
.ck-content hr {
margin: 15px 0;
height: 4px;
background: hsl(0, 0%, 87%);
border: 0;
}
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
.ck-content pre {
padding: 1em;
@@ -438,18 +451,6 @@
padding: 0;
border-radius: 0;
}
/* @ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css */
.ck-content hr {
margin: 15px 0;
height: 4px;
background: hsl(0, 0%, 87%);
border: 0;
}
/* @ckeditor/ckeditor5-mention/theme/mention.css */
.ck-content .mention {
background: var(--ck-color-mention-background);
color: var(--ck-color-mention-text);
}
@media print {
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1034
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.61.2-beta",
"version": "0.61.5-beta",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -31,9 +31,9 @@
"prepare": "husky install || echo 'Husky install failed, expected on flatpak build'"
},
"dependencies": {
"@braintree/sanitize-url": "6.0.2",
"@braintree/sanitize-url": "6.0.4",
"@electron/remote": "2.0.10",
"@excalidraw/excalidraw": "0.15.2",
"@excalidraw/excalidraw": "0.15.3",
"archiver": "5.3.1",
"async-mutex": "0.4.0",
"axios": "1.4.0",
@@ -53,7 +53,7 @@
"escape-html": "1.0.3",
"express": "4.18.2",
"express-partial-content": "1.0.2",
"express-rate-limit": "6.8.1",
"express-rate-limit": "6.9.0",
"express-session": "1.17.3",
"fs-extra": "11.1.1",
"helmet": "7.0.0",
@@ -68,10 +68,10 @@
"jimp": "0.22.10",
"joplin-turndown-plugin-gfm": "1.0.12",
"jsdom": "22.1.0",
"marked": "5.1.2",
"marked": "7.0.3",
"mime-types": "2.1.35",
"multer": "1.4.5-lts.1",
"node-abi": "3.45.0",
"node-abi": "3.46.0",
"normalize-strings": "1.1.1",
"open": "8.4.1",
"rand-token": "1.0.1",
@@ -97,14 +97,14 @@
},
"devDependencies": {
"cross-env": "7.0.3",
"electron": "25.3.2",
"electron": "25.5.0",
"electron-builder": "24.6.3",
"electron-packager": "17.1.1",
"electron-rebuild": "3.2.9",
"eslint": "8.45.0",
"eslint": "8.47.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-prettier": "8.9.0",
"eslint-plugin-import": "2.27.5",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-import": "2.28.0",
"eslint-plugin-jsonc": "2.9.0",
"eslint-plugin-prettier": "5.0.0",
"esm": "3.2.25",
@@ -112,11 +112,11 @@
"jasmine": "5.1.0",
"jsdoc": "4.0.2",
"jsonc-eslint-parser": "2.3.0",
"lint-staged": "13.2.3",
"lint-staged": "14.0.0",
"lorem-ipsum": "2.0.8",
"nodemon": "3.0.1",
"prettier": "3.0.0",
"rcedit": "3.0.1",
"prettier": "3.0.2",
"rcedit": "3.1.0",
"webpack": "5.88.2",
"webpack-cli": "5.1.4"
},

View File

@@ -61,8 +61,8 @@ class AbstractBeccaEntity {
}
/** @protected */
addEntityChange(isDeleted = false) {
entityChangesService.addEntityChange({
putEntityChange(isDeleted = false) {
entityChangesService.putEntityChange({
entityName: this.constructor.entityName,
entityId: this[this.constructor.primaryKeyName],
hash: this.generateHash(isDeleted),
@@ -101,7 +101,7 @@ class AbstractBeccaEntity {
return;
}
this.addEntityChange(false);
this.putEntityChange(false);
if (!cls.isEntityEventsDisabled()) {
const eventPayload = {
@@ -219,7 +219,7 @@ class AbstractBeccaEntity {
// access to the decrypted content
const hash = blobService.calculateContentHash(pojo);
entityChangesService.addEntityChange({
entityChangesService.putEntityChange({
entityName: 'blobs',
entityId: newBlobId,
hash: hash,
@@ -279,7 +279,7 @@ class AbstractBeccaEntity {
log.info(`Marking ${entityName} ${entityId} as deleted`);
this.addEntityChange(true);
this.putEntityChange(true);
eventService.emit(eventService.ENTITY_DELETED, { entityName, entityId, entity: this });
}
@@ -296,7 +296,7 @@ class AbstractBeccaEntity {
log.info(`Marking ${entityName} ${entityId} as deleted`);
this.addEntityChange(true);
this.putEntityChange(true);
eventService.emit(eventService.ENTITY_DELETED, { entityName, entityId, entity: this });
}

View File

@@ -75,7 +75,7 @@ function register(router) {
eu.route(router, 'post' ,'/etapi/refresh-note-ordering/:parentNoteId', (req, res, next) => {
eu.getAndCheckNote(req.params.parentNoteId);
entityChangesService.addNoteReorderingEntityChange(req.params.parentNoteId, "etapi");
entityChangesService.putNoteReorderingEntityChange(req.params.parentNoteId, "etapi");
res.sendStatus(204);
});

View File

@@ -21,6 +21,7 @@ import NoteListWidget from "../widgets/note_list.js";
import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
import LauncherContainer from "../widgets/containers/launcher_container.js";
import RootContainer from "../widgets/containers/root_container.js";
import SharedInfoWidget from "../widgets/shared_info.js";
const MOBILE_CSS = `
<style>
@@ -144,6 +145,7 @@ export default class MobileLayout {
.css("top: 5px;")
)
.child(new CloseDetailButtonWidget().contentSized()))
.child(new SharedInfoWidget())
.child(new FloatingButtons()
.child(new EditButton())
.child(new RelationMapButtons())

View File

@@ -67,7 +67,6 @@ export default class TreeContextMenu {
{ title: "Advanced", uiIcon: "bx bx-empty", enabled: true, items: [
{ title: 'Expand subtree <kbd data-command="expandSubtree"></kbd>', command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
{ title: 'Collapse subtree <kbd data-command="collapseSubtree"></kbd>', command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
{ title: "Force note sync", command: "forceNoteSync", uiIcon: "bx bx-refresh", enabled: noSelectedNotes },
{ title: 'Sort by ... <kbd data-command="sortChildNotes"></kbd>', command: "sortChildNotes", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
{ title: 'Recent changes in subtree', command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes },
{ title: 'Convert to attachment', command: "convertNoteToAttachment", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted }

View File

@@ -31,7 +31,7 @@ async function getRenderedContent(entity, options = {}) {
await renderText(entity, $renderedContent);
}
else if (type === 'code') {
await renderCode(entity, options, $renderedContent);
await renderCode(entity, $renderedContent);
}
else if (type === 'image') {
renderImage(entity, $renderedContent, options);

View File

@@ -148,6 +148,11 @@ function parseNavigationStateFromUrl(url) {
const hash = url.substr(hashIdx + 1); // strip also the initial '#'
const [notePath, paramString] = hash.split("?");
if (!notePath.match(/^[_a-z0-9]{4,}(\/[_a-z0-9]{4,})*$/i)) {
return {};
}
const viewScope = {
viewMode: 'default'
};

View File

@@ -259,8 +259,6 @@ function init() {
};
$.fn.setSelectedExternalLink = function (externalLink) {
console.trace("setSelectedExternalLink");
if (externalLink) {
$(this)
.closest(".input-group")

View File

@@ -18,13 +18,6 @@ async function syncNow(ignoreNotConfigured = false) {
}
}
async function forceNoteSync(noteId) {
await server.post(`sync/force-note-sync/${noteId}`);
toastService.showMessage("Note added to sync queue.");
}
export default {
syncNow,
forceNoteSync
syncNow
};

View File

@@ -37,7 +37,9 @@ export default class NoteListWidget extends NoteContextAwareWidget {
threshold: 0.1
});
observer.observe(this.$widget[0]);
// there seems to be a race condition on Firefox which triggers the observer only before the widget is visible
// (intersection is false). https://github.com/zadam/trilium/issues/4165
setTimeout(() => observer.observe(this.$widget[0]), 10);
}
checkRenderStatus() {

View File

@@ -1564,10 +1564,6 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
this.triggerCommand("showImportDialog", {noteId: node.data.noteId});
}
forceNoteSyncCommand({node}) {
syncService.forceNoteSync(node.data.noteId);
}
editNoteTitleCommand({node}) {
appContext.triggerCommand('focusOnTitle');
}

View File

@@ -2,6 +2,7 @@ import linkService from "../../services/link.js";
import server from "../../services/server.js";
import froca from "../../services/froca.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import options from "../../services/options.js";
const TPL = `
<div class="edited-notes-widget">
@@ -34,7 +35,9 @@ export default class EditedNotesWidget extends NoteContextAwareWidget {
return {
show: this.isEnabled(),
// promoted attributes have priority over edited notes
activate: this.note.getPromotedDefinitionAttributes().length === 0,
activate:
(this.note.getPromotedDefinitionAttributes().length === 0 || !options.is('promotedAttributesOpenInRibbon'))
&& options.is('editedNotesOpenInRibbon'),
title: 'Edited Notes',
icon: 'bx bx-calendar-edit'
};

View File

@@ -4,6 +4,7 @@ import treeService from "../../services/tree.js";
import noteAutocompleteService from "../../services/note_autocomplete.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import attributeService from "../../services/attributes.js";
import options from "../../services/options.js";
const TPL = `
<div>
@@ -62,7 +63,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
return {
show: true,
activate: true,
activate: options.is('promotedAttributesOpenInRibbon'),
title: "Promoted Attributes",
icon: "bx bx-table"
};

View File

@@ -31,6 +31,7 @@ import VacuumDatabaseOptions from "./options/advanced/vacuum_database.js";
import DatabaseAnonymizationOptions from "./options/advanced/database_anonymization.js";
import BackendLogWidget from "./content/backend_log.js";
import AttachmentErasureTimeoutOptions from "./options/other/attachment_erasure_timeout.js";
import RibbonOptions from "./options/appearance/ribbon.js";
const TPL = `<div class="note-detail-content-widget note-detail-printable">
<style>
@@ -57,7 +58,8 @@ const CONTENT_WIDGETS = {
FontsOptions,
ZoomFactorOptions,
NativeTitleBarOptions,
MaxContentWidthOptions
MaxContentWidthOptions,
RibbonOptions
],
_optionsShortcuts: [ KeyboardShortcutsOptions ],
_optionsTextNotes: [

View File

@@ -0,0 +1,34 @@
import OptionsWidget from "../options_widget.js";
const TPL = `
<div class="options-section">
<h4>Ribbon widgets</h4>
<label>
<input type="checkbox" class="promoted-attributes-open-in-ribbon">
Promoted Attributes ribbon tab will automatically open if promoted attributes are present on the note
</label>
<label>
<input type="checkbox" class="edited-notes-open-in-ribbon">
Edited Notes ribbon tab will automatically open on day notes
</label>
</div>`;
export default class RibbonOptions extends OptionsWidget {
doRender() {
this.$widget = $(TPL);
this.$promotedAttributesOpenInRibbon = this.$widget.find(".promoted-attributes-open-in-ribbon");
this.$promotedAttributesOpenInRibbon.on('change', () =>
this.updateCheckboxOption('promotedAttributesOpenInRibbon', this.$promotedAttributesOpenInRibbon));
this.$editedNotesOpenInRibbon = this.$widget.find(".edited-notes-open-in-ribbon");
this.$editedNotesOpenInRibbon.on('change', () =>
this.updateCheckboxOption('editedNotesOpenInRibbon', this.$editedNotesOpenInRibbon));
}
async optionsLoaded(options) {
this.setCheckboxState(this.$promotedAttributesOpenInRibbon, options.promotedAttributesOpenInRibbon);
this.setCheckboxState(this.$editedNotesOpenInRibbon, options.editedNotesOpenInRibbon);
}
}

View File

@@ -722,25 +722,26 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
content: '' !important;
}
.include-note.box-size-small .include-note-content {
/* Using data- attribute to support both CKEditor and readonly view */
.include-note[data-box-size=small] .include-note-content {
max-height: 10em;
overflow: auto;
}
.include-note.box-size-small .include-note-content.type-pdf {
.include-note[data-box-size=small] .include-note-content.type-pdf {
height: 10em; /* PDF is rendered in iframe and must be sized absolutely */
}
.include-note.box-size-medium .include-note-content {
.include-note[data-box-size=medium] .include-note-content {
max-height: 20em;
overflow: auto;
}
.include-note.box-size-medium .include-note-content.type-pdf .rendered-content {
.include-note[data-box-size=medium] .include-note-content.type-pdf .rendered-content {
height: 20em; /* PDF is rendered in iframe and must be sized absolutely */
}
.include-note.box-size-full .include-note-content.type-pdf .rendered-content {
.include-note[data-box-size=full] .include-note-content.type-pdf .rendered-content {
height: 50em; /* PDF is rendered in iframe and it's not possible to put full height so at least a large height */
}

View File

@@ -72,7 +72,7 @@ function moveBranchBeforeNote(req) {
treeService.sortNotesIfNeeded(parentNote.noteId);
// if sorting is not needed, then still the ordering might have changed above manually
entityChangesService.addNoteReorderingEntityChange(parentNote.noteId);
entityChangesService.putNoteReorderingEntityChange(parentNote.noteId);
log.info(`Moved note ${branchToMove.noteId}, branch ${branchId} before note ${beforeBranch.noteId}, branch ${beforeBranchId}`);
@@ -123,7 +123,7 @@ function moveBranchAfterNote(req) {
treeService.sortNotesIfNeeded(parentNote.noteId);
// if sorting is not needed, then still the ordering might have changed above manually
entityChangesService.addNoteReorderingEntityChange(parentNote.noteId);
entityChangesService.putNoteReorderingEntityChange(parentNote.noteId);
log.info(`Moved note ${branchToMove.noteId}, branch ${branchId} after note ${afterNote.noteId}, branch ${afterBranchId}`);

View File

@@ -11,28 +11,26 @@ const ws = require('../../services/ws');
const log = require('../../services/log');
const utils = require('../../services/utils');
const path = require('path');
const BAttribute = require('../../becca/entities/battribute');
const htmlSanitizer = require('../../services/html_sanitizer');
const {formatAttrForSearch} = require("../../services/attribute_formatter");
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
function addClipping(req) {
// if a note under the clipperInbox as the same 'pageUrl' attribute,
// if a note under the clipperInbox has the same 'pageUrl' attribute,
// add the content to that note and clone it under today's inbox
// otherwise just create a new note under today's inbox
let {title, content, pageUrl, images} = req.body;
const clipType = 'clippings';
const clipperInbox = getClipperInboxNote();
const dailyNote = dateNoteService.getDayNote(dateUtils.localNowDate());
pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
let clippingNote = findClippingNote(clipperInbox, pageUrl, clipType);
if (!clippingNote) {
clippingNote = noteService.createNewNote({
parentNoteId: dailyNote.noteId,
parentNoteId: clipperInbox.noteId,
title: title,
content: '',
type: 'text'
@@ -49,8 +47,8 @@ function addClipping(req) {
clippingNote.setContent(`${existingContent}${existingContent.trim() ? "<br>" : ""}${rewrittenContent}`);
if (clippingNote.parentNoteId !== dailyNote.noteId) {
cloneService.cloneNoteToParentNote(clippingNote.noteId, dailyNote.noteId);
if (clippingNote.parentNoteId !== clipperInbox.noteId) {
cloneService.cloneNoteToParentNote(clippingNote.noteId, clipperInbox.noteId);
}
return {
@@ -80,7 +78,7 @@ function getClipperInboxNote() {
let clipperInbox = attributeService.getNoteWithLabel('clipperInbox');
if (!clipperInbox) {
clipperInbox = dateNoteService.getRootCalendarNote();
clipperInbox = dateNoteService.getDayNote(dateUtils.localNowDate());
}
return clipperInbox;
@@ -127,7 +125,10 @@ function createNote(req) {
const existingContent = note.getContent();
const rewrittenContent = processContent(images, note, content);
note.setContent(`${existingContent}${existingContent.trim() ? "<br/>" : ""}${rewrittenContent}`);
const newContent = `${existingContent}${existingContent.trim() ? "<br/>" : ""}${rewrittenContent}`;
note.setContent(newContent);
noteService.asyncPostProcessContent(note, newContent); // to mark attachments as used
return {
noteId: note.noteId
@@ -152,20 +153,9 @@ function processContent(images, note, content) {
const buffer = Buffer.from(dataUrl.split(",")[1], 'base64');
const {note: imageNote, url} = imageService.saveImage(note.noteId, buffer, filename, true);
new BAttribute({
noteId: imageNote.noteId,
type: 'label',
name: 'archived'
}).save(); // so that these image notes don't show up in search / autocomplete
new BAttribute({
noteId: note.noteId,
type: 'relation',
name: 'imageLink',
value: imageNote.noteId
}).save();
const attachment = imageService.saveImageToAttachment(note.noteId, buffer, filename, true);
const sanitizedTitle = attachment.title.replace(/[^a-z0-9-.]/gi, "");
const url = `api/attachments/${attachment.attachmentId}/image/${sanitizedTitle}`;
log.info(`Replacing '${imageId}' with '${url}' in note '${note.noteId}'`);

View File

@@ -3,7 +3,7 @@
const options = require('../../services/options');
const utils = require('../../services/utils');
const dateUtils = require('../../services/date_utils');
const instanceId = require('../../services/member_id');
const instanceId = require('../../services/instance_id');
const passwordEncryptionService = require('../../services/encryption/password_encryption');
const protectedSessionService = require('../../services/protected_session');
const appInfo = require('../../services/app_info');

View File

@@ -55,7 +55,9 @@ const ALLOWED_OPTIONS = new Set([
'eraseUnusedAttachmentsAfterSeconds',
'disableTray',
'customSearchEngineName',
'customSearchEngineUrl'
'customSearchEngineUrl',
'promotedAttributesOpenInRibbon',
'editedNotesOpenInRibbon'
]);
function getOptions() {

View File

@@ -21,8 +21,8 @@ function getRevisions(req) {
LENGTH(blobs.content) AS contentLength
FROM revisions
JOIN blobs ON revisions.blobId = blobs.blobId
WHERE noteId = ?
ORDER BY utcDateCreated DESC`, [req.params.noteId]);
WHERE revisions.noteId = ?
ORDER BY revisions.utcDateCreated DESC`, [req.params.noteId]);
}
function getRevision(req) {

View File

@@ -9,10 +9,8 @@ const optionService = require('../../services/options');
const contentHashService = require('../../services/content_hash');
const log = require('../../services/log');
const syncOptions = require('../../services/sync_options');
const dateUtils = require('../../services/date_utils');
const utils = require('../../services/utils');
const ws = require('../../services/ws');
const becca = require("../../becca/becca");
async function testSync() {
try {
@@ -84,54 +82,14 @@ function forceFullSync() {
syncService.sync();
}
function forceNoteSync(req) {
const noteId = req.params.noteId;
const note = becca.getNote(noteId);
const now = dateUtils.utcNowDateTime();
sql.execute(`UPDATE notes SET utcDateModified = ? WHERE noteId = ?`, [now, noteId]);
entityChangesService.moveEntityChangeToTop('notes', noteId);
sql.execute(`UPDATE blobs SET utcDateModified = ? WHERE blobId = ?`, [now, note.blobId]);
entityChangesService.moveEntityChangeToTop('blobs', note.blobId);
for (const branchId of sql.getColumn("SELECT branchId FROM branches WHERE noteId = ?", [noteId])) {
sql.execute(`UPDATE branches SET utcDateModified = ? WHERE branchId = ?`, [now, branchId]);
entityChangesService.moveEntityChangeToTop('branches', branchId);
}
for (const attributeId of sql.getColumn("SELECT attributeId FROM attributes WHERE noteId = ?", [noteId])) {
sql.execute(`UPDATE attributes SET utcDateModified = ? WHERE attributeId = ?`, [now, attributeId]);
entityChangesService.moveEntityChangeToTop('attributes', attributeId);
}
for (const revisionId of sql.getColumn("SELECT revisionId FROM revisions WHERE noteId = ?", [noteId])) {
sql.execute(`UPDATE revisions SET utcDateModified = ? WHERE revisionId = ?`, [now, revisionId]);
entityChangesService.moveEntityChangeToTop('revisions', revisionId);
}
for (const attachmentId of sql.getColumn("SELECT attachmentId FROM attachments WHERE noteId = ?", [noteId])) {
sql.execute(`UPDATE attachments SET utcDateModified = ? WHERE attachmentId = ?`, [now, attachmentId]);
entityChangesService.moveEntityChangeToTop('attachments', attachmentId);
}
log.info(`Forcing note sync for ${noteId}`);
// not awaiting for the job to finish (will probably take a long time)
syncService.sync();
}
function getChanged(req) {
const startTime = Date.now();
let lastEntityChangeId = parseInt(req.query.lastEntityChangeId);
const clientinstanceId = req.query.instanceId;
const clientInstanceId = req.query.instanceId;
let filteredEntityChanges = [];
while (filteredEntityChanges.length === 0) {
do {
const entityChanges = sql.getRows(`
SELECT *
FROM entity_changes
@@ -144,20 +102,22 @@ function getChanged(req) {
break;
}
filteredEntityChanges = entityChanges.filter(ec => ec.instanceId !== clientinstanceId);
filteredEntityChanges = entityChanges.filter(ec => ec.instanceId !== clientInstanceId);
if (filteredEntityChanges.length === 0) {
lastEntityChangeId = entityChanges[entityChanges.length - 1].id;
}
}
} while (filteredEntityChanges.length === 0);
const entityChangeRecords = syncService.getEntityChangeRecords(filteredEntityChanges);
if (entityChangeRecords.length > 0) {
lastEntityChangeId = entityChangeRecords[entityChangeRecords.length - 1].entityChange.id;
log.info(`Returning ${entityChangeRecords.length} entity changes in ${Date.now() - startTime}ms`);
}
const ret = {
return {
entityChanges: entityChangeRecords,
lastEntityChangeId,
outstandingPullCount: sql.getValue(`
@@ -165,14 +125,8 @@ function getChanged(req) {
FROM entity_changes
WHERE isSynced = 1
AND instanceId != ?
AND id > ?`, [clientinstanceId, lastEntityChangeId])
AND id > ?`, [clientInstanceId, lastEntityChangeId])
};
if (ret.entityChanges.length > 0) {
log.info(`Returning ${ret.entityChanges.length} entity changes in ${Date.now() - startTime}ms`);
}
return ret;
}
const partialRequests = {};
@@ -194,12 +148,12 @@ function update(req) {
}
if (!partialRequests[requestId]) {
throw new Error(`Partial request ${requestId}, index ${pageIndex} of ${pageCount} of pages does not have expected record.`);
throw new Error(`Partial request ${requestId}, page ${pageIndex + 1} of ${pageCount} of pages does not have expected record.`);
}
partialRequests[requestId].payload += req.body;
log.info(`Receiving partial request ${requestId}, page index ${pageIndex} out of ${pageCount} pages.`);
log.info(`Receiving a partial request ${requestId}, page ${pageIndex + 1} out of ${pageCount} pages.`);
if (pageIndex !== pageCount - 1) {
return;
@@ -212,9 +166,11 @@ function update(req) {
const {entities, instanceId} = body;
for (const {entityChange, entity} of entities) {
syncUpdateService.updateEntity(entityChange, entity, instanceId);
}
sql.transactional(() => {
for (const {entityChange, entity} of entities) {
syncUpdateService.updateEntity(entityChange, entity, instanceId);
}
});
}
setInterval(() => {
@@ -241,8 +197,7 @@ function queueSector(req) {
}
function checkEntityChanges() {
const consistencyChecks = require("../../services/consistency_checks");
consistencyChecks.runEntityChangesChecks();
require("../../services/consistency_checks").runEntityChangesChecks();
}
module.exports = {
@@ -251,7 +206,6 @@ module.exports = {
syncNow,
fillEntityChanges,
forceFullSync,
forceNoteSync,
getChanged,
update,
getStats,

View File

@@ -216,7 +216,6 @@ function register(app) {
apiRoute(PST, '/api/sync/now', syncApiRoute.syncNow);
apiRoute(PST, '/api/sync/fill-entity-changes', syncApiRoute.fillEntityChanges);
apiRoute(PST, '/api/sync/force-full-sync', syncApiRoute.forceFullSync);
apiRoute(PST, '/api/sync/force-note-sync/:noteId', syncApiRoute.forceNoteSync);
route(GET, '/api/sync/check', [auth.checkApiAuth], syncApiRoute.checkSync, apiResultHandler);
route(GET, '/api/sync/changed', [auth.checkApiAuth], syncApiRoute.getChanged, apiResultHandler);
route(PUT, '/api/sync/update', [auth.checkApiAuth], syncApiRoute.update, apiResultHandler);

View File

@@ -4,8 +4,8 @@ const build = require('./build');
const packageJson = require('../../package');
const {TRILIUM_DATA_DIR} = require('./data_dir');
const APP_DB_VERSION = 223;
const SYNC_VERSION = 30;
const APP_DB_VERSION = 225;
const SYNC_VERSION = 31;
const CLIPPER_PROTOCOL_VERSION = "1.0";
module.exports = {

View File

@@ -1 +1 @@
module.exports = { buildDate:"2023-07-28T00:06:23+02:00", buildRevision: "ce3834eb9ecd710bde0e6843bd52bb306faf55ce" };
module.exports = { buildDate:"2023-08-16T23:02:15+02:00", buildRevision: "3f7a5504c77263a7118cede5c0d9b450ba37f424" };

View File

@@ -65,6 +65,7 @@ module.exports = [
{ type: 'label', name: 'executeButton'},
{ type: 'label', name: 'executeDescription'},
{ type: 'label', name: 'newNotesOnTop'},
{ type: 'label', name: 'clipperInbox'},
// relation names
{ type: 'relation', name: 'internalLink' },

View File

@@ -161,7 +161,7 @@ function cloneNoteAfter(noteId, afterBranchId) {
sql.execute("UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0",
[afterNote.parentNoteId, afterNote.notePosition]);
eventChangesService.addNoteReorderingEntityChange(afterNote.parentNoteId);
eventChangesService.putNoteReorderingEntityChange(afterNote.parentNoteId);
const branch = new BBranch({
noteId: noteId,

View File

@@ -56,7 +56,7 @@ function getAndClearEntityChangeIds() {
return entityChangeIds;
}
function addEntityChange(entityChange) {
function putEntityChange(entityChange) {
if (namespace.get('ignoreEntityChangeIds')) {
return;
}
@@ -91,6 +91,6 @@ module.exports = {
isEntityEventsDisabled,
reset,
getAndClearEntityChangeIds,
addEntityChange,
putEntityChange,
ignoreEntityChangeIds,
};

View File

@@ -414,7 +414,7 @@ class ConsistencyChecks {
const hash = utils.hash(utils.randomString(10));
entityChangesService.addEntityChange({
entityChangesService.putEntityChange({
entityName: 'blobs',
entityId: blobId,
hash: hash,
@@ -597,23 +597,19 @@ class ConsistencyChecks {
runEntityChangeChecks(entityName, key) {
this.findAndFixIssues(`
SELECT
${key} as entityId
FROM
${entityName}
LEFT JOIN entity_changes ON entity_changes.entityName = '${entityName}'
AND entity_changes.entityId = ${key}
WHERE
entity_changes.id IS NULL`,
SELECT ${key} as entityId
FROM ${entityName}
LEFT JOIN entity_changes ec ON ec.entityName = '${entityName}' AND ec.entityId = ${entityName}.${key}
WHERE ec.id IS NULL`,
({entityId}) => {
const entityRow = sql.getRow(`SELECT * FROM ${entityName} WHERE ${key} = ?`, [entityId]);
if (this.autoFix) {
entityChangesService.addEntityChange({
entityChangesService.putEntityChange({
entityName,
entityId,
hash: utils.randomString(10), // doesn't matter, will force sync, but that's OK
isErased: !!entityRow.isErased,
isErased: false,
utcDateChanged: entityRow.utcDateModified || entityRow.utcDateCreated,
isSynced: entityName !== 'options' || entityRow.isSynced
});
@@ -625,15 +621,13 @@ class ConsistencyChecks {
});
this.findAndFixIssues(`
SELECT
id, entityId
FROM
entity_changes
LEFT JOIN ${entityName} ON entityId = ${key}
SELECT id, entityId
FROM entity_changes
LEFT JOIN ${entityName} ON entityId = ${entityName}.${key}
WHERE
entity_changes.isErased = 0
AND entity_changes.entityName = '${entityName}'
AND ${key} IS NULL`,
AND ${entityName}.${key} IS NULL`,
({id, entityId}) => {
if (this.autoFix) {
sql.execute("DELETE FROM entity_changes WHERE entityName = ? AND entityId = ?", [entityName, entityId]);
@@ -645,11 +639,9 @@ class ConsistencyChecks {
});
this.findAndFixIssues(`
SELECT
id, entityId
FROM
entity_changes
JOIN ${entityName} ON entityId = ${key}
SELECT id, entityId
FROM entity_changes
JOIN ${entityName} ON entityId = ${entityName}.${key}
WHERE
entity_changes.isErased = 1
AND entity_changes.entityName = '${entityName}'`,

View File

@@ -14,7 +14,8 @@ function getEntityHashes() {
const hashRows = sql.getRawRows(`
SELECT entityName,
entityId,
hash
hash,
isErased
FROM entity_changes
WHERE isSynced = 1
AND entityName != 'note_reordering'`);
@@ -25,12 +26,13 @@ function getEntityHashes() {
const hashMap = {};
for (const [entityName, entityId, hash] of hashRows) {
for (const [entityName, entityId, hash, isErased] of hashRows) {
const entityHashMap = hashMap[entityName] = hashMap[entityName] || {};
const sector = entityId[0];
entityHashMap[sector] = (entityHashMap[sector] || "") + hash
// if the entity is erased, its hash is not updated, so it has to be added extra
entityHashMap[sector] = (entityHashMap[sector] || "") + hash + isErased;
}
for (const entityHashMap of Object.values(hashMap)) {

View File

@@ -3,19 +3,19 @@ const dateUtils = require('./date_utils');
const log = require('./log');
const cls = require('./cls');
const utils = require('./utils');
const instanceId = require('./member_id');
const instanceId = require('./instance_id');
const becca = require("../becca/becca");
const blobService = require("../services/blob");
let maxEntityChangeId = 0;
function addEntityChangeWithInstanceId(origEntityChange, instanceId) {
function putEntityChangeWithInstanceId(origEntityChange, instanceId) {
const ec = {...origEntityChange, instanceId};
return addEntityChange(ec);
putEntityChange(ec);
}
function addEntityChange(origEntityChange) {
function putEntityChange(origEntityChange) {
const ec = {...origEntityChange};
delete ec.id;
@@ -32,11 +32,11 @@ function addEntityChange(origEntityChange) {
maxEntityChangeId = Math.max(maxEntityChangeId, ec.id);
cls.addEntityChange(ec);
cls.putEntityChange(ec);
}
function addNoteReorderingEntityChange(parentNoteId, componentId) {
addEntityChange({
function putNoteReorderingEntityChange(parentNoteId, componentId) {
putEntityChange({
entityName: "note_reordering",
entityId: parentNoteId,
hash: 'N/A',
@@ -55,24 +55,24 @@ function addNoteReorderingEntityChange(parentNoteId, componentId) {
});
}
function moveEntityChangeToTop(entityName, entityId) {
const ec = sql.getRow(`SELECT * FROM entity_changes WHERE entityName = ? AND entityId = ?`, [entityName, entityId]);
addEntityChange(ec);
function putEntityChangeForOtherInstances(ec) {
putEntityChange({
...ec,
changeId: null,
instanceId: null
});
}
function addEntityChangesForSector(entityName, sector) {
const startTime = Date.now();
const entityChanges = sql.getRows(`SELECT * FROM entity_changes WHERE entityName = ? AND SUBSTR(entityId, 1, 1) = ?`, [entityName, sector]);
sql.transactional(() => {
for (const ec of entityChanges) {
addEntityChange(ec);
putEntityChange(ec);
}
});
log.info(`Added sector ${sector} of '${entityName}' (${entityChanges.length} entities) to sync queue in ${Date.now() - startTime}ms.`);
log.info(`Added sector ${sector} of '${entityName}' (${entityChanges.length} entities) to the sync queue.`);
}
function cleanupEntityChangesForMissingEntities(entityName, entityPrimaryKey) {
@@ -103,39 +103,34 @@ function fillEntityChanges(entityName, entityPrimaryKey, condition = '') {
createdCount++;
let hash;
let utcDateChanged;
let isSynced;
const ec = {
entityName,
entityId,
isErased: false
};
if (entityName === 'blobs') {
const blob = sql.getRow("SELECT blobId, content, utcDateModified FROM blobs WHERE blobId = ?", [entityId]);
hash = blobService.calculateContentHash(blob);
utcDateChanged = blob.utcDateModified;
isSynced = true; // blobs are always synced
ec.hash = blobService.calculateContentHash(blob);
ec.utcDateChanged = blob.utcDateModified;
ec.isSynced = true; // blobs are always synced
} else {
const entity = becca.getEntity(entityName, entityId);
if (entity) {
hash = entity?.generateHash() || "|deleted";
utcDateChanged = entity?.getUtcDateChanged() || dateUtils.utcNowDateTime();
isSynced = entityName !== 'options' || !!entity?.isSynced;
ec.hash = entity.generateHash() || "|deleted";
ec.utcDateChanged = entity.getUtcDateChanged() || dateUtils.utcNowDateTime();
ec.isSynced = entityName !== 'options' || !!entity.isSynced;
} else {
// entity might be null (not present in becca) when it's deleted
// FIXME: hacky, not sure if it might cause some problems
hash = "deleted";
utcDateChanged = dateUtils.utcNowDateTime();
isSynced = true; // deletable (the ones with isDeleted) entities are synced
ec.hash = "deleted";
ec.utcDateChanged = dateUtils.utcNowDateTime();
ec.isSynced = true; // deletable (the ones with isDeleted) entities are synced
}
}
addEntityChange({
entityName,
entityId,
hash: hash,
isErased: false,
utcDateChanged: utcDateChanged,
isSynced: isSynced
});
putEntityChange(ec);
}
if (createdCount > 0) {
@@ -164,10 +159,10 @@ function recalculateMaxEntityChangeId() {
}
module.exports = {
addNoteReorderingEntityChange,
moveEntityChangeToTop,
addEntityChange,
addEntityChangeWithInstanceId,
putNoteReorderingEntityChange,
putEntityChangeForOtherInstances,
putEntityChange,
putEntityChangeWithInstanceId,
fillAllEntityChanges,
addEntityChangesForSector,
getMaxEntityChangeId: () => maxEntityChangeId,

View File

@@ -37,8 +37,9 @@ function eraseNotes(noteIdsToErase) {
function setEntityChangesAsErased(entityChanges) {
for (const ec of entityChanges) {
ec.isErased = true;
ec.utcDateChanged = dateUtils.utcNowDateTime();
entityChangesService.addEntityChange(ec);
entityChangesService.putEntityChange(ec);
}
}

View File

@@ -301,16 +301,10 @@ function importEnex(taskContext, file, parentNote) {
? resource.title
: `image.${resource.mime.substr(6)}`; // default if real name is not present
const {url, note: imageNote} = imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
for (const attr of resource.attributes) {
if (attr.name !== 'originalFileName') { // this one is already saved in imageService
imageNote.addAttribute(attr.type, attr.name, attr.value);
}
}
updateDates(imageNote, utcDateCreated, utcDateModified);
const attachment = imageService.saveImageToAttachment(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
const sanitizedTitle = attachment.title.replace(/[^a-z0-9-.]/gi, "");
const url = `api/attachments/${attachment.attachmentId}/image/${sanitizedTitle}`;
const imageLink = `<img src="${url}">`;
content = content.replace(mediaRegex, imageLink);

View File

@@ -18,7 +18,7 @@ async function migrate() {
// backup before attempting migration
await backupService.backupNow(
// special name for the pre-0.60 migration to prevent later overwrite
// creating a special backup for versions 0.60.X and older, the changes in 0.61 are major.
currentDbVersion < 214
? `before-migration-v${currentDbVersion}`
: 'before-migration'
@@ -72,6 +72,9 @@ async function migrate() {
}
}
});
log.info("VACUUMing database, this might take a while ...");
sql.execute("VACUUM");
}
function executeMigration(mig) {

View File

@@ -265,7 +265,7 @@ function createNewNoteWithTarget(target, targetBranchId, params) {
const retObject = createNewNote(params);
entityChangesService.addNoteReorderingEntityChange(params.parentNoteId);
entityChangesService.putNoteReorderingEntityChange(params.parentNoteId);
return retObject;
}

View File

@@ -88,7 +88,9 @@ const defaultOptions = [
{ name: 'disableTray', value: 'false', isSynced: false },
{ name: 'eraseUnusedAttachmentsAfterSeconds', value: '2592000', isSynced: true },
{ name: 'customSearchEngineName', value: 'DuckDuckGo', isSynced: true },
{ name: 'customSearchEngineUrl', value: 'https://duckduckgo.com/?q={keyword}', isSynced: true }
{ name: 'customSearchEngineUrl', value: 'https://duckduckgo.com/?q={keyword}', isSynced: true },
{ name: 'promotedAttributesOpenInRibbon', value: 'true', isSynced: true },
{ name: 'editedNotesOpenInRibbon', value: 'true', isSynced: true }
];
function initStartupOptions() {

View File

@@ -3,6 +3,7 @@
const log = require('./log');
const sql = require('./sql');
const protectedSessionService = require("./protected_session");
const dateUtils = require("./date_utils");
/**
* @param {BNote} note
@@ -40,7 +41,7 @@ function eraseRevisions(revisionIdsToErase) {
log.info(`Removing note revisions: ${JSON.stringify(revisionIdsToErase)}`);
sql.executeMany(`DELETE FROM revisions WHERE revisionId IN (???)`, revisionIdsToErase);
sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'revisions' AND entityId IN (???)`, revisionIdsToErase);
sql.executeMany(`UPDATE entity_changes SET isErased = 1, utcDateChanged = '${dateUtils.utcNowDateTime()}' WHERE entityName = 'revisions' AND entityId IN (???)`, revisionIdsToErase);
}
module.exports = {

View File

@@ -4,7 +4,7 @@ const log = require('./log');
const sql = require('./sql');
const optionService = require('./options');
const utils = require('./utils');
const instanceId = require('./member_id');
const instanceId = require('./instance_id');
const dateUtils = require('./date_utils');
const syncUpdateService = require('./sync_update');
const contentHashService = require('./content_hash');
@@ -54,12 +54,12 @@ async function sync() {
});
}
catch (e) {
// we're dynamically switching whether we're using proxy or not based on whether we encountered error with the current method
proxyToggle = !proxyToggle;
if (e.message &&
(e.message.includes('ECONNREFUSED') ||
e.message.includes('ERR_') || // node network errors
e.message.includes('Bad Gateway'))) {
if (e.message?.includes('ECONNREFUSED') ||
e.message?.includes('ERR_') || // node network errors
e.message?.includes('Bad Gateway')) {
ws.syncFailed();
@@ -108,7 +108,7 @@ async function doLogin() {
});
if (resp.instanceId === instanceId) {
throw new Error(`Sync server has member ID '${resp.instanceId}' which is also local. This usually happens when the sync client is (mis)configured to sync with itself (URL points back to client) instead of the correct sync server.`);
throw new Error(`Sync server has instance ID '${resp.instanceId}' which is also local. This usually happens when the sync client is (mis)configured to sync with itself (URL points back to client) instead of the correct sync server.`);
}
syncContext.instanceId = resp.instanceId;
@@ -146,17 +146,19 @@ async function pullChanges(syncContext) {
sql.transactional(() => {
for (const {entityChange, entity} of entityChanges) {
const changeAppliedAlready = entityChange.changeId
&& !!sql.getValue("SELECT id FROM entity_changes WHERE changeId = ?", [entityChange.changeId]);
&& !!sql.getValue("SELECT 1 FROM entity_changes WHERE changeId = ?", [entityChange.changeId]);
if (!changeAppliedAlready) {
if (!atLeastOnePullApplied) { // send only for first
ws.syncPullInProgress();
atLeastOnePullApplied = true;
}
syncUpdateService.updateEntity(entityChange, entity, syncContext.instanceId);
if (changeAppliedAlready) {
continue;
}
if (!atLeastOnePullApplied) { // send only for first
ws.syncPullInProgress();
atLeastOnePullApplied = true;
}
syncUpdateService.updateEntity(entityChange, entity, syncContext.instanceId);
}
if (lastSyncedPull !== lastEntityChangeId) {
@@ -254,7 +256,7 @@ async function checkContentHash(syncContext) {
const failedChecks = contentHashService.checkContentHashes(resp.entityHashes);
if (failedChecks.length > 0) {
// before requeuing sectors, make sure the entity changes are correct
// before re-queuing sectors, make sure the entity changes are correct
const consistencyChecks = require("./consistency_checks");
consistencyChecks.runEntityChangesChecks();
@@ -351,7 +353,8 @@ function getEntityChangeRecords(entityChanges) {
length += JSON.stringify(record).length;
if (length > 1000000) {
if (length > 1_000_000) {
// each sync request/response should have at most ~1 MB.
break;
}
}

View File

@@ -4,98 +4,83 @@ const entityChangesService = require('./entity_changes');
const eventService = require('./events');
const entityConstructor = require("../becca/entity_constructor");
function updateEntity(entityChange, entityRow, instanceId) {
// can be undefined for options with isSynced=false
if (!entityRow) {
if (entityChange.isSynced) {
if (entityChange.isErased) {
eraseEntity(entityChange, instanceId);
}
else {
log.info(`Encountered synced non-erased entity change without entity: ${JSON.stringify(entityChange)}`);
}
}
else if (entityChange.entityName !== 'options') {
log.info(`Encountered unsynced non-option entity change without entity: ${JSON.stringify(entityChange)}`);
}
return;
function updateEntity(remoteEC, remoteEntityRow, instanceId) {
if (!remoteEntityRow && remoteEC.entityName === 'options') {
return; // can be undefined for options with isSynced=false
}
const updated = entityChange.entityName === 'note_reordering'
? updateNoteReordering(entityChange, entityRow, instanceId)
: updateNormalEntity(entityChange, entityRow, instanceId);
const updated = remoteEC.entityName === 'note_reordering'
? updateNoteReordering(remoteEC, remoteEntityRow, instanceId)
: updateNormalEntity(remoteEC, remoteEntityRow, instanceId);
if (updated) {
if (entityRow.isDeleted) {
if (remoteEntityRow?.isDeleted) {
eventService.emit(eventService.ENTITY_DELETE_SYNCED, {
entityName: entityChange.entityName,
entityId: entityChange.entityId
entityName: remoteEC.entityName,
entityId: remoteEC.entityId
});
}
else if (!entityChange.isErased) {
else if (!remoteEC.isErased) {
eventService.emit(eventService.ENTITY_CHANGE_SYNCED, {
entityName: entityChange.entityName,
entityRow
entityName: remoteEC.entityName,
entityRow: remoteEntityRow
});
}
}
}
function updateNormalEntity(remoteEntityChange, remoteEntityRow, instanceId) {
const localEntityChange = sql.getRow(`
SELECT utcDateChanged, hash, isErased
FROM entity_changes
WHERE entityName = ? AND entityId = ?`, [remoteEntityChange.entityName, remoteEntityChange.entityId]);
function updateNormalEntity(remoteEC, remoteEntityRow, instanceId) {
const localEC = sql.getRow(`SELECT * FROM entity_changes WHERE entityName = ? AND entityId = ?`, [remoteEC.entityName, remoteEC.entityId]);
if (localEntityChange && !localEntityChange.isErased && remoteEntityChange.isErased) {
sql.transactional(() => {
const primaryKey = entityConstructor.getEntityFromEntityName(remoteEntityChange.entityName).primaryKeyName;
sql.execute(`DELETE FROM ${remoteEntityChange.entityName} WHERE ${primaryKey} = ?`, remoteEntityChange.entityId);
entityChangesService.addEntityChangeWithInstanceId(remoteEntityChange, instanceId);
});
if (!localEC?.isErased && remoteEC.isErased) {
eraseEntity(remoteEC, instanceId);
return true;
} else if (localEC?.isErased && !remoteEC.isErased) {
// on this side, we can't unerase the entity, so force the entity to be erased on the other side.
entityChangesService.putEntityChangeForOtherInstances(localEC);
return false;
}
if (!localEntityChange
|| localEntityChange.utcDateChanged < remoteEntityChange.utcDateChanged
|| localEntityChange.hash !== remoteEntityChange.hash // sync error, we should still update
if (!localEC
|| localEC.utcDateChanged < remoteEC.utcDateChanged
|| (localEC.utcDateChanged === remoteEC.utcDateChanged && localEC.hash !== remoteEC.hash) // sync error, we should still update
) {
if (remoteEntityChange.entityName === 'blobs') {
if (remoteEC.entityName === 'blobs' && remoteEntityRow.content !== null) {
// we always use a Buffer object which is different from normal saving - there we use a simple string type for
// "string notes". The problem is that in general, it's not possible to detect whether a blob content
// is string note or note (syncs can arrive out of order)
remoteEntityRow.content = remoteEntityRow.content === null ? null : Buffer.from(remoteEntityRow.content, 'base64');
remoteEntityRow.content = Buffer.from(remoteEntityRow.content, 'base64');
if (remoteEntityRow.content?.byteLength === 0) {
if (remoteEntityRow.content.byteLength === 0) {
// there seems to be a bug which causes empty buffer to be stored as NULL which is then picked up as inconsistency
// (possibly not a problem anymore with the newer better-sqlite3)
remoteEntityRow.content = "";
}
}
sql.transactional(() => {
sql.replace(remoteEntityChange.entityName, remoteEntityRow);
sql.replace(remoteEC.entityName, remoteEntityRow);
entityChangesService.addEntityChangeWithInstanceId(remoteEntityChange, instanceId);
});
entityChangesService.putEntityChangeWithInstanceId(remoteEC, instanceId);
return true;
} else if (localEC.hash !== remoteEC.hash && localEC.utcDateChanged > remoteEC.utcDateChanged) {
// the change on our side is newer than on the other side, so the other side should update
entityChangesService.putEntityChangeForOtherInstances(localEC);
return false;
}
return false;
}
function updateNoteReordering(entityChange, entity, instanceId) {
sql.transactional(() => {
for (const key in entity) {
sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity[key], key]);
}
function updateNoteReordering(remoteEC, remoteEntityRow, instanceId) {
for (const key in remoteEntityRow) {
sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [remoteEntityRow[key], key]);
}
entityChangesService.addEntityChangeWithInstanceId(entityChange, instanceId);
});
entityChangesService.putEntityChangeWithInstanceId(remoteEC, instanceId);
return true;
}
@@ -109,21 +94,19 @@ function eraseEntity(entityChange, instanceId) {
"attributes",
"revisions",
"attachments",
"blobs",
"blobs"
];
if (!entityNames.includes(entityName)) {
log.error(`Cannot erase entity '${entityName}', id '${entityId}'`);
log.error(`Cannot erase entity '${entityName}', id '${entityId}'.`);
return;
}
const keyName = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName;
const primaryKeyName = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName;
sql.execute(`DELETE FROM ${entityName} WHERE ${keyName} = ?`, [entityId]);
sql.execute(`DELETE FROM ${entityName} WHERE ${primaryKeyName} = ?`, [entityId]);
eventService.emit(eventService.ENTITY_DELETE_SYNCED, { entityName, entityId });
entityChangesService.addEntityChangeWithInstanceId(entityChange, instanceId);
entityChangesService.putEntityChangeWithInstanceId(entityChange, instanceId);
}
module.exports = {

View File

@@ -165,7 +165,7 @@ function sortNotes(parentNoteId, customSortBy = 'title', reverse = false, folder
}
if (someBranchUpdated) {
entityChangesService.addNoteReorderingEntityChange(parentNoteId);
entityChangesService.putNoteReorderingEntityChange(parentNoteId);
}
});
}

View File

@@ -28,8 +28,13 @@ function hashedBlobId(content) {
// sha512 is faster than sha256
const base64Hash = crypto.createHash('sha512').update(content).digest('base64');
// 20 characters of base64 gives us 120 bit of entropy which is plenty enough
return base64Hash.substr(0, 20);
// we don't want such + and / in the IDs
const kindaBase62Hash = base64Hash
.replace('+', 'X')
.replace('/', 'Y');
// 20 characters of base62 gives us ~120 bit of entropy which is plenty enough
return kindaBase62Hash.substr(0, 20);
}
function toBase64(plainText) {