Compare commits

...

48 Commits

Author SHA1 Message Date
zadam
21e77c83fc release 0.37.8 2019-12-03 22:31:20 +01:00
zadam
b0310e34e2 fix CSS loading from subdomain, #741 2019-12-03 22:13:02 +01:00
zadam
761c51069a use max-height for promoted attributes, #739 2019-12-03 19:47:53 +01:00
zadam
4dc285d84f serve favicon from relative path 2019-12-03 19:31:58 +01:00
zadam
a1402c7c66 release 0.37.7 2019-12-02 23:09:42 +01:00
zadam
6ba3e5ab7f backport fix from master to avoid doubled attributes inherited from multiple paths 2019-12-02 23:07:19 +01:00
zadam
f740e52ebf correctly respect label @disableVersioning
(cherry picked from commit dc063983ea)
2019-12-02 23:06:06 +01:00
zadam
e9454e4db7 fix SQL console scrolling
(cherry picked from commit 749bb90713)
2019-12-02 23:05:05 +01:00
zadam
bfc7570e14 don't convert MD to HTML if "import markdown as text" is not selected, closes #733 2019-12-01 11:27:22 +01:00
zadam
5de92171a7 use owned attributes where it's a better fit 2019-12-01 10:28:05 +01:00
zadam
29c5e394ab generate document now creates also labels and relations 2019-12-01 10:20:18 +01:00
zadam
07b3d11fe5 fix generate new document script 2019-12-01 09:19:16 +01:00
zadam
67663fba50 fixes 2019-11-30 11:36:36 +01:00
zadam
995ebbf577 removed foreign keys PRAGMAs since foreign key constraints are not used anymore 2019-11-30 10:41:53 +01:00
zadam
d0e6be3e0c entity stat as part of consistency checks 2019-11-30 09:15:08 +01:00
zadam
01370a5968 fix anonymization according to latest schema 2019-11-29 21:42:24 +01:00
zadam
5b30291601 release 0.37.6 2019-11-26 22:50:08 +01:00
zadam
5193f073e9 if there's no updated field use created #725 2019-11-26 22:47:54 +01:00
zadam
6c7d8a9667 preserve dateCreated and dateModified in ENEX import, fixes #725 2019-11-26 22:02:21 +01:00
zadam
5e9bedd903 fixed sidebar switch in the options dialog 2019-11-26 20:46:49 +01:00
zadam
e712990c03 don't remove active tab after deleting note to preserve tab state, fixes #727 2019-11-26 20:42:34 +01:00
zadam
91487b338a make the context menu scrollable when exceeding total window height, closes #723 2019-11-26 19:55:52 +01:00
zadam
3ff24d53e5 fix decrypting note titles on the server installation, closes #724 2019-11-26 19:49:52 +01:00
zadam
94c904fb40 fix context menu over root, closes #726 2019-11-26 19:42:47 +01:00
zadam
5f258fbbbf release 0.37.5 2019-11-25 22:46:15 +01:00
zadam
bf9ad976b9 fixing non-root path issues, #404 2019-11-25 21:44:46 +01:00
zadam
434d8ef48c added extra autofixers for completely missing note_contents or note_revision_contents row 2019-11-23 19:56:52 +01:00
zadam
c8ba07a4ae fix migration script in case of not fully consistent database (missing note_contents for note). closes #717 2019-11-23 11:13:57 +01:00
zadam
38e7649ac3 release 0.37.4 2019-11-22 22:38:03 +01:00
zadam
7a2c7edd7e allow multiple instances of @in operator, closes #716 2019-11-22 21:17:46 +01:00
zadam
cfb850acb2 download fixes for the sub-domain web deployment 2019-11-22 20:35:17 +01:00
zadam
a16aaf7a81 fix setup on non-root paths #404 2019-11-22 20:24:49 +01:00
zadam
522f71cb91 fix tree clipboard 2019-11-20 19:24:23 +01:00
zadam
d357943ebb release 0.37.3 2019-11-19 23:05:54 +01:00
zadam
07043fb177 switch search in subtree to ctrl+shift+s to stay consistent with ctrl+s 2019-11-19 23:04:43 +01:00
zadam
1f8d382b1f added "search in subtree" context menu, #534 2019-11-19 21:11:20 +01:00
zadam
61e8cbbcba add log for content hash failures 2019-11-19 19:07:14 +01:00
zadam
86c5dd6494 fix recent changes, closes #713 2019-11-19 19:02:16 +01:00
zadam
c5acb7fc9b release 0.37.2 2019-11-18 23:04:09 +01:00
zadam
834e1f7253 fix activating tab when app was closed last time with "new tab" 2019-11-17 10:24:06 +01:00
zadam
1a87190f43 added IN operator to search, closes #534 2019-11-17 09:07:35 +01:00
zadam
a1262aaaf3 release 0.37.1-beta 2019-11-16 19:09:52 +01:00
zadam
1838f097e5 added getAllNotePaths, fixes #708 2019-11-16 19:07:32 +01:00
zadam
95d0ad1cad simpler ENEX parsing 2019-11-16 18:13:00 +01:00
zadam
f9b5e473f2 added missing options 2019-11-16 17:03:18 +01:00
zadam
35e9df6170 fixes in the handling of cssClass and iconClass when working with newly created / single note 2019-11-16 11:23:28 +01:00
zadam
c5e040c4a4 updated demo document with icon classes 2019-11-16 09:57:19 +01:00
zadam
e00ab5dbf9 new "iconClass" property on note_short, now together with cssClass respecting template relationship, #707 2019-11-16 08:59:27 +01:00
74 changed files with 1103 additions and 500 deletions

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<dataSource name="document.db">
<database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.16">
<database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.17">
<root id="1">
<ServerVersion>3.25.1</ServerVersion>
</root>

Binary file not shown.

View File

@@ -20,7 +20,7 @@ SELECT noteId, title, -1, isProtected, type, mime, hash, isDeleted, isErased, da
DROP TABLE notes;
ALTER TABLE notes_mig RENAME TO notes;
UPDATE notes SET contentLength = (SELECT COALESCE(LENGTH(content), 0) FROM note_contents WHERE note_contents.noteId = notes.noteId);
UPDATE notes SET contentLength = COALESCE((SELECT COALESCE(LENGTH(content), 0) FROM note_contents WHERE note_contents.noteId = notes.noteId), -1);
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (`isDeleted`);
CREATE INDEX `IDX_notes_title` ON `notes` (`title`);

View File

@@ -28,7 +28,7 @@
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>ApiToken<span class="signature">(apiTokenId, token, isDeleted, utcDateCreated)</span><span class="type-signature"></span></h2>
<h2><span class="attribs"><span class="type-signature"></span></span>ApiToken<span class="signature">()</span><span class="type-signature"></span></h2>
<div class="class-description">ApiToken is an entity representing token used to authenticate against Trilium API from client applications. Currently used only by Trilium Sender.</div>
@@ -45,7 +45,7 @@
<h4 class="name" id="ApiToken"><span class="type-signature"></span>new ApiToken<span class="signature">(apiTokenId, token, isDeleted, utcDateCreated)</span><span class="type-signature"></span></h4>
<h4 class="name" id="ApiToken"><span class="type-signature"></span>new ApiToken<span class="signature">()</span><span class="type-signature"></span></h4>
@@ -60,10 +60,15 @@
<h5>Parameters:</h5>
<h5 class="subsection-title">Properties:</h5>
<table class="params">
<table class="props">
<thead>
<tr>
@@ -180,8 +185,6 @@
<dl class="details">

View File

@@ -28,7 +28,7 @@
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>Attribute<span class="signature">(attributeId, noteId, type, name, value, position, isInheritable, isDeleted, utcDateCreated, utcDateModified)</span><span class="type-signature"></span></h2>
<h2><span class="attribs"><span class="type-signature"></span></span>Attribute<span class="signature">()</span><span class="type-signature"></span></h2>
<div class="class-description">Attribute is key value pair owned by a note.</div>
@@ -45,7 +45,7 @@
<h4 class="name" id="Attribute"><span class="type-signature"></span>new Attribute<span class="signature">(attributeId, noteId, type, name, value, position, isInheritable, isDeleted, utcDateCreated, utcDateModified)</span><span class="type-signature"></span></h4>
<h4 class="name" id="Attribute"><span class="type-signature"></span>new Attribute<span class="signature">()</span><span class="type-signature"></span></h4>
@@ -60,10 +60,15 @@
<h5>Parameters:</h5>
<h5 class="subsection-title">Properties:</h5>
<table class="params">
<table class="props">
<thead>
<tr>
@@ -318,8 +323,6 @@
<dl class="details">

View File

@@ -28,7 +28,7 @@
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>Branch<span class="signature">(branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, utcDateModified, utcDateCreated)</span><span class="type-signature"></span></h2>
<h2><span class="attribs"><span class="type-signature"></span></span>Branch<span class="signature">()</span><span class="type-signature"></span></h2>
<div class="class-description">Branch represents note's placement in the tree - it's essentially pair of noteId and parentNoteId.
Each note can have multiple (at least one) branches, meaning it can be placed into multiple places in the tree.</div>
@@ -46,7 +46,7 @@ Each note can have multiple (at least one) branches, meaning it can be placed in
<h4 class="name" id="Branch"><span class="type-signature"></span>new Branch<span class="signature">(branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, utcDateModified, utcDateCreated)</span><span class="type-signature"></span></h4>
<h4 class="name" id="Branch"><span class="type-signature"></span>new Branch<span class="signature">()</span><span class="type-signature"></span></h4>
@@ -61,10 +61,15 @@ Each note can have multiple (at least one) branches, meaning it can be placed in
<h5>Parameters:</h5>
<h5 class="subsection-title">Properties:</h5>
<table class="params">
<table class="props">
<thead>
<tr>
@@ -296,8 +301,6 @@ Each note can have multiple (at least one) branches, meaning it can be placed in
<dl class="details">

View File

@@ -224,6 +224,29 @@
<tr>
<td class="name"><code>contentLength</code></td>
<td class="type">
<span class="param-type">int</span>
</td>
<td class="description last">length of content</td>
</tr>
<tr>
<td class="name"><code>isProtected</code></td>
@@ -419,7 +442,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line42">line 42</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line43">line 43</a>
</li></ul></dd>
@@ -486,6 +509,112 @@
<h4 class="name" id="getAllNotePaths"><span class="type-signature">(async) </span>getAllNotePaths<span class="signature">()</span><span class="type-signature"> &rarr; {Promise.&lt;Array.&lt;Array.&lt;string>>>}</span></h4>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line734">line 734</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<div class="param-desc">
- array of notePaths (each represented by array of noteIds constituting the particular note path)
</div>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">Promise.&lt;Array.&lt;Array.&lt;string>>></span>
</dd>
</dl>
<h4 class="name" id="getAttribute"><span class="type-signature">(async) </span>getAttribute<span class="signature">(type, name)</span><span class="type-signature"> &rarr; {Promise.&lt;<a href="Attribute.html">Attribute</a>>}</span></h4>
@@ -606,7 +735,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line383">line 383</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line385">line 385</a>
</li></ul></dd>
@@ -773,7 +902,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line237">line 237</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line239">line 239</a>
</li></ul></dd>
@@ -951,7 +1080,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line394">line 394</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line396">line 396</a>
</li></ul></dd>
@@ -1057,7 +1186,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line678">line 678</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line680">line 680</a>
</li></ul></dd>
@@ -1159,7 +1288,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line706">line 706</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line708">line 708</a>
</li></ul></dd>
@@ -1265,7 +1394,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line692">line 692</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line694">line 694</a>
</li></ul></dd>
@@ -1371,7 +1500,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line73">line 73</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line74">line 74</a>
</li></ul></dd>
@@ -1473,7 +1602,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line578">line 578</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line580">line 580</a>
</li></ul></dd>
@@ -1706,7 +1835,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line601">line 601</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line603">line 603</a>
</li></ul></dd>
@@ -1904,7 +2033,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line639">line 639</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line641">line 641</a>
</li></ul></dd>
@@ -2102,7 +2231,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line648">line 648</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line650">line 650</a>
</li></ul></dd>
@@ -2204,7 +2333,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line108">line 108</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line109">line 109</a>
</li></ul></dd>
@@ -2355,7 +2484,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line489">line 489</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line491">line 491</a>
</li></ul></dd>
@@ -2522,7 +2651,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line262">line 262</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line264">line 264</a>
</li></ul></dd>
@@ -2689,7 +2818,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line254">line 254</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line256">line 256</a>
</li></ul></dd>
@@ -2844,7 +2973,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line501">line 501</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line503">line 503</a>
</li></ul></dd>
@@ -2956,7 +3085,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line665">line 665</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line667">line 667</a>
</li></ul></dd>
@@ -3058,7 +3187,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line220">line 220</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line222">line 222</a>
</li></ul></dd>
@@ -3166,7 +3295,7 @@ This method can be significantly faster than the getAttribute()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line198">line 198</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line200">line 200</a>
</li></ul></dd>
@@ -3274,7 +3403,7 @@ This method can be significantly faster than the getAttributes()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line718">line 718</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line720">line 720</a>
</li></ul></dd>
@@ -3429,7 +3558,7 @@ This method can be significantly faster than the getAttributes()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line495">line 495</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line497">line 497</a>
</li></ul></dd>
@@ -3596,7 +3725,7 @@ This method can be significantly faster than the getAttributes()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line293">line 293</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line295">line 295</a>
</li></ul></dd>
@@ -3763,7 +3892,7 @@ This method can be significantly faster than the getAttributes()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line270">line 270</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line272">line 272</a>
</li></ul></dd>
@@ -3918,7 +4047,7 @@ This method can be significantly faster than the getAttributes()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line513">line 513</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line515">line 515</a>
</li></ul></dd>
@@ -4088,7 +4217,7 @@ This method can be significantly faster than the getAttributes()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line278">line 278</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line280">line 280</a>
</li></ul></dd>
@@ -4239,7 +4368,7 @@ This method can be significantly faster than the getAttributes()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line507">line 507</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line509">line 509</a>
</li></ul></dd>
@@ -4349,7 +4478,7 @@ This method can be significantly faster than the getAttributes()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line655">line 655</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line657">line 657</a>
</li></ul></dd>
@@ -4451,7 +4580,7 @@ This method can be significantly faster than the getAttributes()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line177">line 177</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line179">line 179</a>
</li></ul></dd>
@@ -4557,7 +4686,7 @@ This method can be significantly faster than the getAttributes()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line229">line 229</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line231">line 231</a>
</li></ul></dd>
@@ -4735,7 +4864,7 @@ This method can be significantly faster than the getAttributes()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line374">line 374</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line376">line 376</a>
</li></ul></dd>
@@ -4841,7 +4970,7 @@ This method can be significantly faster than the getAttributes()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line685">line 685</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line687">line 687</a>
</li></ul></dd>
@@ -4996,7 +5125,7 @@ This method can be significantly faster than the getAttributes()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line477">line 477</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line479">line 479</a>
</li></ul></dd>
@@ -5151,7 +5280,7 @@ This method can be significantly faster than the getAttributes()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line483">line 483</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line485">line 485</a>
</li></ul></dd>
@@ -5262,7 +5391,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line301">line 301</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line303">line 303</a>
</li></ul></dd>
@@ -5346,7 +5475,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line167">line 167</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line169">line 169</a>
</li></ul></dd>
@@ -5452,7 +5581,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line159">line 159</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line161">line 161</a>
</li></ul></dd>
@@ -5558,7 +5687,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line154">line 154</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line156">line 156</a>
</li></ul></dd>
@@ -5664,7 +5793,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line149">line 149</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line151">line 151</a>
</li></ul></dd>
@@ -5770,7 +5899,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line172">line 172</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line174">line 174</a>
</li></ul></dd>
@@ -5876,7 +6005,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line306">line 306</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line308">line 308</a>
</li></ul></dd>
@@ -6105,7 +6234,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line460">line 460</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line462">line 462</a>
</li></ul></dd>
@@ -6303,7 +6432,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line564">line 564</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line566">line 566</a>
</li></ul></dd>
@@ -6501,7 +6630,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line573">line 573</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line575">line 575</a>
</li></ul></dd>
@@ -6730,7 +6859,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line426">line 426</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line428">line 428</a>
</li></ul></dd>
@@ -6832,7 +6961,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line115">line 115</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line116">line 116</a>
</li></ul></dd>
@@ -6934,7 +7063,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line144">line 144</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line146">line 146</a>
</li></ul></dd>
@@ -7132,7 +7261,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line546">line 546</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line548">line 548</a>
</li></ul></dd>
@@ -7330,7 +7459,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line555">line 555</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line557">line 557</a>
</li></ul></dd>
@@ -7590,7 +7719,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line409">line 409</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line411">line 411</a>
</li></ul></dd>
@@ -7819,7 +7948,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line527">line 527</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line529">line 529</a>
</li></ul></dd>
@@ -8048,7 +8177,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line537">line 537</a>
<a href="entities_note.js.html">entities/note.js</a>, <a href="entities_note.js.html#line539">line 539</a>
</li></ul></dd>

View File

@@ -28,7 +28,7 @@
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>NoteRevision<span class="signature">(noteRevisionId, noteId, type, mime, title, isProtected, dateLastEdited, dateCreated, utcDateLastEdited, utcDateCreated, utcDateModified)</span><span class="type-signature"></span></h2>
<h2><span class="attribs"><span class="type-signature"></span></span>NoteRevision<span class="signature">()</span><span class="type-signature"></span></h2>
<div class="class-description">NoteRevision represents snapshot of note's title and content at some point in the past. It's used for seamless note versioning.</div>
@@ -45,7 +45,7 @@
<h4 class="name" id="NoteRevision"><span class="type-signature"></span>new NoteRevision<span class="signature">(noteRevisionId, noteId, type, mime, title, isProtected, dateLastEdited, dateCreated, utcDateLastEdited, utcDateCreated, utcDateModified)</span><span class="type-signature"></span></h4>
<h4 class="name" id="NoteRevision"><span class="type-signature"></span>new NoteRevision<span class="signature">()</span><span class="type-signature"></span></h4>
@@ -60,10 +60,15 @@
<h5>Parameters:</h5>
<h5 class="subsection-title">Properties:</h5>
<table class="params">
<table class="props">
<thead>
<tr>
@@ -193,6 +198,52 @@
<td class="description last"></td>
</tr>
<tr>
<td class="name"><code>contentLength</code></td>
<td class="type">
<span class="param-type">int</span>
</td>
<td class="description last"></td>
</tr>
<tr>
<td class="name"><code>isErased</code></td>
<td class="type">
<span class="param-type">boolean</span>
</td>
<td class="description last"></td>
</tr>
@@ -206,7 +257,7 @@
<td class="type">
<span class="param-type">string</span>
<span class="param-type">boolean</span>
@@ -341,8 +392,6 @@
<dl class="details">
@@ -372,7 +421,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line33">line 33</a>
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line35">line 35</a>
</li></ul></dd>
@@ -487,7 +536,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line67">line 67</a>
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line69">line 69</a>
</li></ul></dd>
@@ -589,7 +638,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line53">line 53</a>
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line55">line 55</a>
</li></ul></dd>
@@ -695,7 +744,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line102">line 102</a>
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line104">line 104</a>
</li></ul></dd>

View File

@@ -28,7 +28,7 @@
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>Option<span class="signature">(name, value, isSynced, utcDateModified, utcDateCreated)</span><span class="type-signature"></span></h2>
<h2><span class="attribs"><span class="type-signature"></span></span>Option<span class="signature">()</span><span class="type-signature"></span></h2>
<div class="class-description">Option represents name-value pair, either directly configurable by the user or some system property.</div>
@@ -45,7 +45,7 @@
<h4 class="name" id="Option"><span class="type-signature"></span>new Option<span class="signature">(name, value, isSynced, utcDateModified, utcDateCreated)</span><span class="type-signature"></span></h4>
<h4 class="name" id="Option"><span class="type-signature"></span>new Option<span class="signature">()</span><span class="type-signature"></span></h4>
@@ -60,10 +60,15 @@
<h5>Parameters:</h5>
<h5 class="subsection-title">Properties:</h5>
<table class="params">
<table class="props">
<thead>
<tr>
@@ -203,8 +208,6 @@
<dl class="details">

View File

@@ -28,7 +28,7 @@
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>RecentNote<span class="signature">(noteId, notePath, isDeleted, utcDateModified)</span><span class="type-signature"></span></h2>
<h2><span class="attribs"><span class="type-signature"></span></span>RecentNote<span class="signature">()</span><span class="type-signature"></span></h2>
<div class="class-description">RecentNote represents recently visited note.</div>
@@ -45,7 +45,7 @@
<h4 class="name" id="RecentNote"><span class="type-signature"></span>new RecentNote<span class="signature">(noteId, notePath, isDeleted, utcDateModified)</span><span class="type-signature"></span></h4>
<h4 class="name" id="RecentNote"><span class="type-signature"></span>new RecentNote<span class="signature">()</span><span class="type-signature"></span></h4>
@@ -60,10 +60,15 @@
<h5>Parameters:</h5>
<h5 class="subsection-title">Properties:</h5>
<table class="params">
<table class="props">
<thead>
<tr>
@@ -180,8 +185,6 @@
<dl class="details">

View File

@@ -34,10 +34,10 @@ const dateUtils = require('../services/date_utils');
/**
* ApiToken is an entity representing token used to authenticate against Trilium API from client applications. Currently used only by Trilium Sender.
*
* @param {string} apiTokenId - primary key
* @param {string} token
* @param {boolean} isDeleted - true if API token is deleted
* @param {string} utcDateCreated
* @property {string} apiTokenId - primary key
* @property {string} token
* @property {boolean} isDeleted - true if API token is deleted
* @property {string} utcDateCreated
*
* @extends Entity
*/

View File

@@ -36,16 +36,16 @@ const sql = require('../services/sql');
/**
* Attribute is key value pair owned by a note.
*
* @param {string} attributeId
* @param {string} noteId
* @param {string} type
* @param {string} name
* @param {string} value
* @param {int} position
* @param {boolean} isInheritable
* @param {boolean} isDeleted
* @param {string} utcDateCreated
* @param {string} utcDateModified
* @property {string} attributeId
* @property {string} noteId
* @property {string} type
* @property {string} name
* @property {string} value
* @property {int} position
* @property {boolean} isInheritable
* @property {boolean} isDeleted
* @property {string} utcDateCreated
* @property {string} utcDateModified
*
* @extends Entity
*/

View File

@@ -37,15 +37,15 @@ const sql = require('../services/sql');
* Branch represents note's placement in the tree - it's essentially pair of noteId and parentNoteId.
* Each note can have multiple (at least one) branches, meaning it can be placed into multiple places in the tree.
*
* @param {string} branchId - primary key
* @param {string} noteId
* @param {string} parentNoteId
* @param {int} notePosition
* @param {string} prefix
* @param {boolean} isExpanded
* @param {boolean} isDeleted
* @param {string} utcDateModified
* @param {string} utcDateCreated
* @property {string} branchId - primary key
* @property {string} noteId
* @property {string} parentNoteId
* @property {int} notePosition
* @property {string} prefix
* @property {boolean} isExpanded
* @property {boolean} isDeleted
* @property {string} utcDateModified
* @property {string} utcDateCreated
*
* @extends Entity
*/

View File

@@ -49,6 +49,7 @@ const RELATION_DEFINITION = 'relation-definition';
* @property {string} type - one of "text", "code", "file" or "render"
* @property {string} mime - MIME type, e.g. "text/html"
* @property {string} title - note title
* @property {int} contentLength - length of content
* @property {boolean} isProtected - true if note is protected
* @property {boolean} isDeleted - true if note is deleted
* @property {boolean} isErased - true if note's content is erased after it has been deleted
@@ -143,6 +144,7 @@ class Note extends Entity {
async setContent(content) {
// force updating note itself so that dateModified is represented correctly even for the content
this.forcedChange = true;
this.contentLength = content.length;
await this.save();
this.content = content;
@@ -754,6 +756,26 @@ class Note extends Entity {
AND parent_notes.isDeleted = 0`, [this.noteId]);
}
/**
* @return {Promise&lt;string[][]>} - array of notePaths (each represented by array of noteIds constituting the particular note path)
*/
async getAllNotePaths() {
if (this.noteId === 'root') {
return [['root']];
}
const notePaths = [];
for (const parentNote of await this.getParentNotes()) {
for (const parentPath of await parentNote.getAllNotePaths()) {
parentPath.push(this.noteId);
notePaths.push(parentPath);
}
}
return notePaths;
}
beforeSaving() {
if (!this.isDeleted) {
this.isDeleted = false;
@@ -767,6 +789,10 @@ class Note extends Entity {
this.utcDateCreated = dateUtils.utcNowDateTime();
}
if (this.contentLength === undefined) {
this.contentLength = -1;
}
super.beforeSaving();
if (this.isChanged) {

View File

@@ -39,24 +39,26 @@ const syncTableService = require('../services/sync_table');
/**
* NoteRevision represents snapshot of note's title and content at some point in the past. It's used for seamless note versioning.
*
* @param {string} noteRevisionId
* @param {string} noteId
* @param {string} type
* @param {string} mime
* @param {string} title
* @param {string} isProtected
* @param {string} dateLastEdited
* @param {string} dateCreated
* @param {string} utcDateLastEdited
* @param {string} utcDateCreated
* @param {string} utcDateModified
* @property {string} noteRevisionId
* @property {string} noteId
* @property {string} type
* @property {string} mime
* @property {string} title
* @property {int} contentLength
* @property {boolean} isErased
* @property {boolean} isProtected
* @property {string} dateLastEdited
* @property {string} dateCreated
* @property {string} utcDateLastEdited
* @property {string} utcDateCreated
* @property {string} utcDateModified
*
* @extends Entity
*/
class NoteRevision extends Entity {
static get entityName() { return "note_revisions"; }
static get primaryKeyName() { return "noteRevisionId"; }
static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "contentLength", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified"]; }
static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "contentLength", "isErased", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified"]; }
constructor(row) {
super(row);
@@ -130,7 +132,7 @@ class NoteRevision extends Entity {
async setContent(content) {
// force updating note itself so that utcDateModified is represented correctly even for the content
this.forcedChange = true;
this.contentLength = content.length;
this.contentLength = content === null ? 0 : content.length;
await this.save();
this.content = content;
@@ -156,6 +158,14 @@ class NoteRevision extends Entity {
await syncTableService.addNoteRevisionContentSync(this.noteRevisionId);
}
beforeSaving() {
super.beforeSaving();
if (this.isChanged) {
this.utcDateModified = dateUtils.utcNowDateTime();
}
}
// cannot be static!
updatePojo(pojo) {
if (pojo.isProtected) {

View File

@@ -34,11 +34,11 @@ const dateUtils = require('../services/date_utils');
/**
* Option represents name-value pair, either directly configurable by the user or some system property.
*
* @param {string} name
* @param {string} value
* @param {boolean} isSynced
* @param {string} utcDateModified
* @param {string} utcDateCreated
* @property {string} name
* @property {string} value
* @property {boolean} isSynced
* @property {string} utcDateModified
* @property {string} utcDateCreated
*
* @extends Entity
*/

View File

@@ -34,10 +34,10 @@ const dateUtils = require('../services/date_utils');
/**
* RecentNote represents recently visited note.
*
* @param {string} noteId
* @param {string} notePath
* @param {boolean} isDeleted
* @param {string} utcDateModified
* @property {string} noteId
* @property {string} notePath
* @property {boolean} isDeleted
* @property {string} utcDateModified
*
* @extends Entity
*/

View File

@@ -278,7 +278,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line33">line 33</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line35">line 35</a>
</li></ul></dd>
@@ -346,7 +346,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line40">line 40</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line44">line 44</a>
</li></ul></dd>
@@ -414,7 +414,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line46">line 46</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line50">line 50</a>
</li></ul></dd>
@@ -432,123 +432,7 @@
<h4 class="name" id="cssClass"><span class="type-signature"></span>cssClass<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="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line35">line 35</a>
</li></ul></dd>
</dl>
<h4 class="name" id="isDeleted"><span class="type-signature"></span>isDeleted<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="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line31">line 31</a>
</li></ul></dd>
</dl>
<h4 class="name" id="isProtected"><span class="type-signature"></span>isProtected<span class="type-signature"></span></h4>
<h4 class="name" id="contentLength"><span class="type-signature"></span>contentLength<span class="type-signature"></span></h4>
@@ -606,6 +490,238 @@
<h4 class="name" id="cssClass"><span class="type-signature"></span>cssClass<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="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line37">line 37</a>
</li></ul></dd>
</dl>
<h4 class="name" id="iconClass"><span class="type-signature"></span>iconClass<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="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line39">line 39</a>
</li></ul></dd>
</dl>
<h4 class="name" id="isDeleted"><span class="type-signature"></span>isDeleted<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="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line33">line 33</a>
</li></ul></dd>
</dl>
<h4 class="name" id="isProtected"><span class="type-signature"></span>isProtected<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="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line27">line 27</a>
</li></ul></dd>
</dl>
<h4 class="name" id="mime"><span class="type-signature"></span>mime<span class="type-signature"></span></h4>
@@ -646,7 +762,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line29">line 29</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line31">line 31</a>
</li></ul></dd>
@@ -772,7 +888,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line38">line 38</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line42">line 42</a>
</li></ul></dd>
@@ -840,7 +956,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line43">line 43</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line47">line 47</a>
</li></ul></dd>
@@ -956,7 +1072,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line27">line 27</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line29">line 29</a>
</li></ul></dd>
@@ -1104,7 +1220,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line216">line 216</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line220">line 220</a>
</li></ul></dd>
@@ -1271,7 +1387,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line156">line 156</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line160">line 160</a>
</li></ul></dd>
@@ -1445,7 +1561,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line227">line 227</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line231">line 231</a>
</li></ul></dd>
@@ -1551,7 +1667,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line113">line 113</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line117">line 117</a>
</li></ul></dd>
@@ -1653,7 +1769,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line125">line 125</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line129">line 129</a>
</li></ul></dd>
@@ -1755,7 +1871,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line143">line 143</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line147">line 147</a>
</li></ul></dd>
@@ -1857,7 +1973,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line148">line 148</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line152">line 152</a>
</li></ul></dd>
@@ -2008,7 +2124,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line249">line 249</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line253">line 253</a>
</li></ul></dd>
@@ -2175,7 +2291,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line182">line 182</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line186">line 186</a>
</li></ul></dd>
@@ -2342,7 +2458,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line174">line 174</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line178">line 178</a>
</li></ul></dd>
@@ -2497,7 +2613,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line261">line 261</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line265">line 265</a>
</li></ul></dd>
@@ -2603,7 +2719,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line133">line 133</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line137">line 137</a>
</li></ul></dd>
@@ -2705,7 +2821,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line138">line 138</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line142">line 142</a>
</li></ul></dd>
@@ -2856,7 +2972,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line255">line 255</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line259">line 259</a>
</li></ul></dd>
@@ -3023,7 +3139,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line198">line 198</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line202">line 202</a>
</li></ul></dd>
@@ -3190,7 +3306,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line190">line 190</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line194">line 194</a>
</li></ul></dd>
@@ -3345,7 +3461,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line273">line 273</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line277">line 277</a>
</li></ul></dd>
@@ -3515,7 +3631,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line283">line 283</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line287">line 287</a>
</li></ul></dd>
@@ -3666,7 +3782,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line267">line 267</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line271">line 271</a>
</li></ul></dd>
@@ -3776,7 +3892,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line307">line 307</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line311">line 311</a>
</li></ul></dd>
@@ -3950,7 +4066,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line207">line 207</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line211">line 211</a>
</li></ul></dd>
@@ -4056,7 +4172,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line120">line 120</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line124">line 124</a>
</li></ul></dd>
@@ -4207,7 +4323,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line237">line 237</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line241">line 241</a>
</li></ul></dd>
@@ -4362,7 +4478,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line243">line 243</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line247">line 247</a>
</li></ul></dd>
@@ -4473,7 +4589,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line298">line 298</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line302">line 302</a>
</li></ul></dd>
@@ -4557,7 +4673,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line88">line 88</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line92">line 92</a>
</li></ul></dd>

View File

@@ -49,6 +49,8 @@ class NoteShort {
this.noteId = row.noteId;
/** @param {string} */
this.title = row.title;
/** @param {int} */
this.contentLength = row.contentLength;
/** @param {boolean} */
this.isProtected = row.isProtected;
/** @param {string} one of 'text', 'code', 'file' or 'render' */
@@ -61,6 +63,8 @@ class NoteShort {
this.archived = row.archived;
/** @param {string} */
this.cssClass = row.cssClass;
/** @param {string} */
this.iconClass = row.iconClass;
/** @type {string[]} */
this.parents = [];

View File

@@ -115,7 +115,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
const button = $('&lt;button>')
.addClass("btn btn-sm")
.click(opts.action);
.on('click', opts.action);
if (opts.icon) {
button.append($("&lt;span>").addClass("bx bx-" + opts.icon))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -498,7 +498,7 @@
t._started = false;
onRenderStop();
} else {
setImmediate(step);
requestIdleCallback(step, { timeout: 10 });
}
}

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "trilium",
"version": "0.36.5",
"version": "0.37.7",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.37.0-beta",
"version": "0.37.8",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -74,8 +74,7 @@
"turndown": "5.0.3",
"turndown-plugin-gfm": "1.0.2",
"unescape": "1.0.1",
"ws": "7.2.0",
"xml2js": "0.4.22"
"ws": "7.2.0"
},
"devDependencies": {
"electron": "6.0.12",

View File

@@ -334,6 +334,11 @@ class Note extends Entity {
// we order by noteId so that attributes from same note stay together. Actual noteId ordering doesn't matter.
const filteredAttributes = attributes.filter((attr, index) => {
// if this exact attribute already appears then don't include it (can happen via cloning)
if (attributes.findIndex(it => it.attributeId === attr.attributeId) !== index) {
return false;
}
if (attr.isDefinition()) {
const firstDefinitionIndex = attributes.findIndex(el => el.type === attr.type && el.name === attr.name);
@@ -728,6 +733,26 @@ class Note extends Entity {
AND parent_notes.isDeleted = 0`, [this.noteId]);
}
/**
* @return {Promise<string[][]>} - array of notePaths (each represented by array of noteIds constituting the particular note path)
*/
async getAllNotePaths() {
if (this.noteId === 'root') {
return [['root']];
}
const notePaths = [];
for (const parentNote of await this.getParentNotes()) {
for (const parentPath of await parentNote.getAllNotePaths()) {
parentPath.push(this.noteId);
notePaths.push(parentPath);
}
}
return notePaths;
}
beforeSaving() {
if (!this.isDeleted) {
this.isDeleted = false;
@@ -768,6 +793,7 @@ class Note extends Entity {
delete pojo.isContentAvailable;
delete pojo.__attributeCache;
delete pojo.content;
/** zero references to contentHash, probably can be removed */
delete pojo.contentHash;
}
}

View File

@@ -78,7 +78,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
};
for (const appCssNoteId of window.appCssNoteIds) {
cssLoader.requireCss(`/api/notes/download/${appCssNoteId}`);
cssLoader.requireCss(`api/notes/download/${appCssNoteId}`);
}
const wikiBaseUrl = "https://github.com/zadam/trilium/wiki/";

View File

@@ -7,6 +7,7 @@ import utils from "../services/utils.js";
import linkService from "../services/link.js";
import libraryLoader from "../services/library_loader.js";
import noteAutocompleteService from "../services/note_autocomplete.js";
import treeService from "../services/tree.js";
const $dialog = $("#attributes-dialog");
const $saveAttributesButton = $("#save-attributes-button");
@@ -175,6 +176,7 @@ function AttributesModel() {
ctx.attributes.refreshAttributes();
noteDetailService.reload();
treeService.reload();
};
function addLastEmptyRow() {

View File

@@ -74,7 +74,7 @@ $form.on('submit', () => {
function exportBranch(branchId, type, format, version) {
taskId = utils.randomString(10);
const url = utils.getHost() + `/api/notes/${branchId}/export/${type}/${format}/${version}/${taskId}`;
const url = utils.getUrlForDownload(`api/notes/${branchId}/export/${type}/${format}/${version}/${taskId}`);
utils.download(url);
}

View File

@@ -102,7 +102,9 @@ async function setContentPane() {
const $downloadButton = $('<button class="btn btn-sm btn-primary" type="button">Download</button>');
$downloadButton.on('click', () => {
utils.download(utils.getHost() + `/api/notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}/download`);
const url = utils.getUrlForDownload(`api/notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}/download`);
utils.download(url);
});
$titleButtons.append($downloadButton);

View File

@@ -131,7 +131,7 @@ export default class ApperanceOptions {
if (noteId) {
// make sure the CSS is loaded
// if the CSS has been loaded and then updated then the changes won't take effect though
cssLoader.requireCss(`/api/notes/download/${noteId}`);
cssLoader.requireCss(`api/notes/download/${noteId}`);
}
this.$body.addClass("theme-" + newTheme);

View File

@@ -93,7 +93,7 @@ export default class SidebarOptions {
this.$sidebarMinWidth.val(options.sidebarMinWidth);
this.$sidebarWidthPercent.val(options.sidebarWidthPercent);
if (parseInt(options.showSidebarInNewTab)) {
if (options.showSidebarInNewTab === 'true') {
this.$showSidebarInNewTab.attr("checked", "checked");
}
else {

View File

@@ -35,6 +35,8 @@ class NoteShort {
this.archived = row.archived;
/** @param {string} */
this.cssClass = row.cssClass;
/** @param {string} */
this.iconClass = row.iconClass;
/** @type {string[]} */
this.parents = [];

View File

@@ -4,83 +4,97 @@ import cloningService from "./cloning.js";
import toastService from "./toast.js";
import hoistedNoteService from "./hoisted_note.js";
let clipboardIds = [];
/*
* Clipboard contains node keys which are not stable. If a (part of the) tree is reloaded,
* node keys in the clipboard might not exist anymore. Code here should then be ready to deal
* with this.
*/
let clipboardNodeKeys = [];
let clipboardMode = null;
async function pasteAfter(node) {
async function pasteAfter(afterNode) {
if (isClipboardEmpty()) {
return;
}
if (clipboardMode === 'cut') {
const nodes = clipboardIds.map(nodeKey => treeUtils.getNodeByKey(nodeKey));
const nodes = clipboardNodeKeys.map(nodeKey => treeUtils.getNodeByKey(nodeKey));
await treeChangesService.moveAfterNode(nodes, node);
await treeChangesService.moveAfterNode(nodes, afterNode);
clipboardIds = [];
clipboardNodeKeys = [];
clipboardMode = null;
}
else if (clipboardMode === 'copy') {
for (const noteId of clipboardIds) {
await cloningService.cloneNoteAfter(noteId, node.data.branchId);
for (const nodeKey of clipboardNodeKeys) {
const clipNode = treeUtils.getNodeByKey(nodeKey);
await cloningService.cloneNoteAfter(clipNode.data.noteId, afterNode.data.branchId);
}
// copy will keep clipboardIds and clipboardMode so it's possible to paste into multiple places
}
else if (clipboardIds.length === 0) {
// just do nothing
}
else {
toastService.throwError("Unrecognized clipboard mode=" + clipboardMode);
}
}
async function pasteInto(node) {
async function pasteInto(parentNode) {
if (isClipboardEmpty()) {
return;
}
if (clipboardMode === 'cut') {
const nodes = clipboardIds.map(nodeKey => treeUtils.getNodeByKey(nodeKey));
const nodes = clipboardNodeKeys.map(nodeKey => treeUtils.getNodeByKey(nodeKey));
await treeChangesService.moveToNode(nodes, node);
await treeChangesService.moveToNode(nodes, parentNode);
await node.setExpanded(true);
await parentNode.setExpanded(true);
clipboardIds = [];
clipboardNodeKeys = [];
clipboardMode = null;
}
else if (clipboardMode === 'copy') {
for (const noteId of clipboardIds) {
await cloningService.cloneNoteTo(noteId, node.data.noteId);
for (const nodeKey of clipboardNodeKeys) {
const clipNode = treeUtils.getNodeByKey(nodeKey);
await cloningService.cloneNoteTo(clipNode.data.noteId, parentNode.data.noteId);
}
await node.setExpanded(true);
await parentNode.setExpanded(true);
// copy will keep clipboardIds and clipboardMode so it's possible to paste into multiple places
}
else if (clipboardIds.length === 0) {
// just do nothing
}
else {
toastService.throwError("Unrecognized clipboard mode=" + mode);
}
}
function copy(nodes) {
clipboardIds = nodes.map(node => node.data.noteId);
clipboardNodeKeys = nodes.map(node => node.key);
clipboardMode = 'copy';
toastService.showMessage("Note(s) have been copied into clipboard.");
}
function cut(nodes) {
clipboardIds = nodes
clipboardNodeKeys = nodes
.filter(node => node.data.noteId !== hoistedNoteService.getHoistedNoteNoPromise())
.filter(node => node.getParent().data.noteType !== 'search')
.map(node => node.data.noteId);
.map(node => node.key);
if (clipboardIds.length > 0) {
if (clipboardNodeKeys.length > 0) {
clipboardMode = 'cut';
toastService.showMessage("Note(s) have been cut into clipboard.");
}
}
function isEmpty() {
return clipboardIds.length === 0;
function isClipboardEmpty() {
clipboardNodeKeys = clipboardNodeKeys.filter(key => !!treeUtils.getNodeByKey(key));
return clipboardNodeKeys.length === 0;
}
export default {
@@ -88,5 +102,5 @@ export default {
pasteInto,
cut,
copy,
isEmpty
isClipboardEmpty
}

View File

@@ -4,7 +4,6 @@ import server from './server.js';
import ws from "./ws.js";
import treeCache from "./tree_cache.js";
import NoteFull from "../entities/note_full.js";
import bundleService from "./bundle.js";
import utils from "./utils.js";
import contextMenuService from "./context_menu.js";
import treeUtils from "./tree_utils.js";
@@ -274,7 +273,9 @@ async function filterTabs(noteId) {
async function noteDeleted(noteId) {
for (const tc of tabContexts) {
if (tc.notePath && tc.notePath.split("/").includes(noteId)) {
// not removing active even if it contains deleted note since that one will move to another note (handled by deletion logic)
// and we would lose tab context state (e.g. sidebar visibility)
if (!tc.isActive() && tc.notePath && tc.notePath.split("/").includes(noteId)) {
await tabRow.removeTab(tc.$tab[0]);
}
}

View File

@@ -185,8 +185,7 @@ class NoteDetailBook {
}
else if (type === 'file') {
function getFileUrl() {
// electron needs absolute URL so we extract current host, port, protocol
return utils.getHost() + "/api/notes/" + note.noteId + "/download";
return utils.getUrlForDownload("api/notes/" + note.noteId + "/download");
}
const $downloadButton = $('<button class="file-download btn btn-primary" type="button">Download</button>');

View File

@@ -87,8 +87,7 @@ class NoteDetailFile {
}
getFileUrl() {
// electron needs absolute URL so we extract current host, port, protocol
return utils.getHost() + "/api/notes/" + this.ctx.note.noteId + "/download";
return utils.getUrlForDownload("api/notes/" + this.ctx.note.noteId + "/download");
}
show() {}

View File

@@ -98,8 +98,7 @@ class NoteDetailImage {
}
getFileUrl() {
// electron needs absolute URL so we extract current host, port, protocol
return utils.getHost() + `/api/notes/${this.ctx.note.noteId}/download`;
return utils.getUrlForDownload(`api/notes/${this.ctx.note.noteId}/download`);
}
show() {}

View File

@@ -142,6 +142,12 @@ async function refreshSearch() {
toastService.showMessage("Saved search note refreshed.");
}
function searchInSubtree(noteId) {
showSearch();
$searchInput.val(`@in=${noteId} @text*=*`);
}
function init() {
const hashValue = document.location.hash ? document.location.hash.substr(1) : ""; // strip initial #
@@ -178,5 +184,6 @@ export default {
refreshSearch,
doSearch,
init,
searchInSubtree,
getHelpText: () => helpText
};

View File

@@ -246,11 +246,15 @@ class TabContext {
}
setCurrentNotePathToHash() {
if (this.$tab[0] === this.tabRow.activeTabEl) {
if (this.isActive()) {
document.location.hash = (this.notePath || "") + "-" + this.tabId;
}
}
isActive() {
return this.$tab[0] === this.tabRow.activeTabEl;
}
setupClasses() {
for (const clazz of Array.from(this.$tab[0].classList)) { // create copy to safely iterate over while removing classes
if (clazz !== 'note-tab') {

View File

@@ -41,7 +41,10 @@ const NOTE_TYPE_ICONS = {
async function getIcon(note) {
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
if (note.noteId === 'root') {
if (note.iconClass) {
return note.iconClass;
}
else if (note.noteId === 'root') {
return "bx bx-chevrons-right";
}
else if (note.noteId === hoistedNoteId) {

View File

@@ -107,6 +107,10 @@ class TreeCache {
if (note.type === 'search') {
const searchResults = await server.get('search-note/' + note.noteId);
if (!searchResults) {
throw new Error(`Search note ${note.noteId} failed.`);
}
// force to load all the notes at once instead of one by one
await treeCache.getNotes(searchResults.map(res => res.noteId));
@@ -144,7 +148,7 @@ class TreeCache {
else {
return this.notes[noteId];
}
}).filter(note => note !== null);
}).filter(note => !!note);
}
/** @return {Promise<boolean>} */

View File

@@ -9,6 +9,7 @@ import hoistedNoteService from './hoisted_note.js';
import noteDetailService from './note_detail.js';
import clipboard from './clipboard.js';
import protectedSessionHolder from "./protected_session_holder.js";
import searchNotesService from "./search_notes.js";
class TreeContextMenu {
constructor(node) {
@@ -41,7 +42,7 @@ class TreeContextMenu {
|| (selNodes.length === 1 && selNodes[0] === this.node);
const notSearch = note.type !== 'search';
const parentNotSearch = parentNote.type !== 'search';
const parentNotSearch = !parentNote || parentNote.type !== 'search';
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
return [
@@ -55,6 +56,8 @@ class TreeContextMenu {
{ title: "Delete <kbd>Delete</kbd>", cmd: "delete", uiIcon: "trash",
enabled: isNotRoot && !isHoisted && parentNotSearch },
{ title: "----" },
{ title: "Search in subtree <kbd>Ctrl+Shift+S</kbd>", cmd: "searchInSubtree", uiIcon: "search",
enabled: notSearch && noSelectedNotes },
isHoisted ? null : { title: "Hoist note <kbd>Ctrl-H</kbd>", cmd: "hoist", uiIcon: "empty", enabled: noSelectedNotes && notSearch },
!isHoisted || !isNotRoot ? null : { title: "Unhoist note <kbd>Ctrl-H</kbd>", cmd: "unhoist", uiIcon: "arrow-up" },
{ title: "Edit branch prefix <kbd>F2</kbd>", cmd: "editBranchPrefix", uiIcon: "empty",
@@ -72,11 +75,11 @@ class TreeContextMenu {
{ title: "Move to ... <kbd>Ctrl+Shift+X</kbd>", cmd: "moveTo", uiIcon: "empty",
enabled: isNotRoot && !isHoisted && parentNotSearch },
{ title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "paste",
enabled: !clipboard.isEmpty() && notSearch && noSelectedNotes },
enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes },
{ title: "Paste after", cmd: "pasteAfter", uiIcon: "paste",
enabled: !clipboard.isEmpty() && isNotRoot && parentNotSearch && noSelectedNotes },
enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes },
{ title: "Duplicate note here", cmd: "duplicateNote", uiIcon: "empty",
enabled: noSelectedNotes && parentNotSearch && (!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) },
enabled: noSelectedNotes && parentNotSearch && isNotRoot && !isHoisted && (!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) },
{ title: "----" },
{ title: "Export", cmd: "export", uiIcon: "empty",
enabled: notSearch && noSelectedNotes },
@@ -177,6 +180,9 @@ class TreeContextMenu {
treeService.duplicateNote(this.node.data.noteId, branch.parentNoteId);
}
else if (cmd === "searchInSubtree") {
searchNotesService.searchInSubtree(this.node.data.noteId);
}
else {
ws.logError("Unknown command: " + cmd);
}

View File

@@ -4,6 +4,7 @@ import treeService from "./tree.js";
import hoistedNoteService from "./hoisted_note.js";
import clipboard from "./clipboard.js";
import treeCache from "./tree_cache.js";
import searchNoteService from "./search_notes.js";
const keyBindings = {
"del": node => {
@@ -167,6 +168,11 @@ const keyBindings = {
"down": node => {
node.navigate($.ui.keyCode.DOWN, true).then(treeService.clearSelectedNodes);
return false;
},
"ctrl+shift+s": node => {
searchNoteService.searchInSubtree(node.data.noteId);
return false;
}
};

View File

@@ -214,6 +214,20 @@ async function clearBrowserCache() {
}
}
/**
* @param url - should be without initial slash!!!
*/
function getUrlForDownload(url) {
if (isElectron()) {
// electron needs absolute URL so we extract current host, port, protocol
return getHost() + '/' + url;
}
else {
// web server can be deployed on subdomain so we need to use relative path
return url;
}
}
export default {
reloadApp,
parseDate,
@@ -230,7 +244,6 @@ export default {
escapeHtml,
stopWatch,
formatLabel,
getHost,
download,
toObject,
randomString,
@@ -245,5 +258,6 @@ export default {
getMimeTypeClass,
closeActiveDialog,
isHtmlEmpty,
clearBrowserCache
clearBrowserCache,
getUrlForDownload
};

View File

@@ -127,11 +127,13 @@ async function consumeSyncData() {
}
function connectWebSocket() {
const protocol = document.location.protocol === 'https:' ? 'wss' : 'ws';
const loc = window.location;
const webSocketUri = (loc.protocol === "https:" ? "wss:" : "ws:")
+ "//" + loc.host + loc.pathname;
// use wss for secure messaging
const ws = new WebSocket(protocol + "://" + location.host);
ws.onopen = () => console.debug(utils.now(), "Connected to server with WebSocket");
const ws = new WebSocket(webSocketUri);
ws.onopen = () => console.debug(utils.now(), `Connected to server ${webSocketUri} with WebSocket`);
ws.onmessage = handleMessage;
// we're not handling ws.onclose here because reconnection is done in sendPing()

View File

@@ -76,12 +76,12 @@ function SetupModel() {
}
// not using server.js because it loads too many dependencies
$.post('/api/setup/new-document', {
$.post('api/setup/new-document', {
username: username,
password: password1,
theme: theme
}).then(() => {
window.location.replace("/");
window.location.replace("./");
});
}
else if (this.setupType() === 'sync-from-server') {
@@ -128,10 +128,10 @@ function SetupModel() {
}
async function checkOutstandingSyncs() {
const { stats, initialized } = await $.get('/api/sync/stats');
const { stats, initialized } = await $.get('api/sync/stats');
if (initialized) {
window.location.replace("/");
window.location.replace("./");
}
const totalOutstandingSyncs = stats.outstandingPushes + stats.outstandingPulls;

View File

@@ -89,6 +89,11 @@ body {
font-size: inherit;
}
#context-menu-container {
max-height: 100vh;
overflow: auto; /* make it scrollable when exceeding total height of the window */
}
#context-menu-container, #context-menu-container .dropdown-menu {
padding: 3px 0 0;
z-index: 1111;

View File

@@ -411,6 +411,10 @@ div.ui-tooltip {
height: 150px;
}
#sql-console-query .CodeMirror-scroll {
min-height: inherit !important;
}
.btn {
border-radius: var(--button-border-radius);
}
@@ -460,9 +464,10 @@ button.icon-button {
margin: auto;
/* setting the display to block since "table" doesn't support scrolling */
display: block;
flex-basis: content;
flex-shrink: 1;
/** flex-basis: content; - use once "content" is implemented by chrome */
flex-shrink: 0;
flex-grow: 0;
max-height: 30%;
overflow: auto;
}

View File

@@ -22,7 +22,7 @@ async function getNote(req) {
}
}
note.cssClass = (await note.getLabels("cssClass")).map(label => label.value).join(" ");
await treeService.setCssClassesToNotes([note]);
return note;
}
@@ -58,7 +58,7 @@ async function createNote(req) {
const { note, branch } = await noteService.createNewNote(parentNoteId, newNote, req);
note.cssClass = (await note.getLabels("cssClass")).map(label => label.value).join(" ");
await treeService.setCssClassesToNotes([note]);
return {
note,

View File

@@ -17,8 +17,8 @@ async function getRecentChanges() {
FROM
note_revisions
JOIN notes USING(noteId)
ORDER BY
utcDateCreated DESC
ORDER BY
note_revisions.utcDateCreated DESC
LIMIT 1000
)
UNION ALL SELECT * FROM (

View File

@@ -2,57 +2,11 @@
const sql = require('../../services/sql');
const optionService = require('../../services/options');
const protectedSessionService = require('../../services/protected_session');
const noteCacheService = require('../../services/note_cache');
async function getNotes(noteIds) {
// we return also deleted notes which have been specifically asked for
const notes = await sql.getManyRows(`
SELECT
noteId,
title,
isProtected,
type,
mime,
isDeleted
FROM notes
WHERE noteId IN (???)`, noteIds);
const cssClassLabels = await sql.getManyRows(`
SELECT noteId, value FROM attributes WHERE isDeleted = 0 AND type = 'label'
AND name = 'cssClass' AND noteId IN (???)`, noteIds);
for (const label of cssClassLabels) {
// FIXME: inefficient!
const note = notes.find(note => note.noteId === label.noteId);
if (!note) {
continue;
}
if (note.cssClass) {
note.cssClass += " " + label.value;
}
else {
note.cssClass = label.value;
}
}
protectedSessionService.decryptNotes(notes);
await noteCacheService.loadedPromise;
notes.forEach(note => {
note.isProtected = !!note.isProtected;
note.archived = noteCacheService.isArchived(note.noteId)
});
return notes;
}
const treeService = require('../../services/tree');
async function getNotesAndBranches(noteIds) {
noteIds = Array.from(new Set(noteIds));
const notes = await getNotes(noteIds);
const notes = await treeService.getNotes(noteIds);
noteIds = notes.map(n => n.noteId);

View File

@@ -18,7 +18,10 @@ async function anonymize() {
await db.run("UPDATE notes SET title = 'title'");
await db.run("UPDATE note_contents SET content = 'text'");
await db.run("UPDATE note_revisions SET title = 'title', content = 'text'");
await db.run("UPDATE note_revisions SET title = 'title'");
await db.run("UPDATE note_revision_contents SET content = 'title'");
await db.run("UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label'");
await db.run("UPDATE attributes SET name = 'name' WHERE type = 'relation'");
await db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL");
await db.run(`UPDATE options SET value = 'anonymized' WHERE name IN
('documentSecret', 'encryptedDataKey', 'passwordVerificationHash',

View File

@@ -19,6 +19,8 @@ const BUILTIN_ATTRIBUTES = [
{ type: 'label', name: 'appTheme' },
{ type: 'label', name: 'hidePromotedAttributes' },
{ type: 'label', name: 'readOnly' },
{ type: 'label', name: 'cssClass' },
{ type: 'label', name: 'iconClass' },
{ type: 'label', name: 'run', isDangerous: true },
{ type: 'label', name: 'customRequestHandler', isDangerous: true },
{ type: 'label', name: 'customResourceProvider', isDangerous: true },

View File

@@ -1 +1 @@
module.exports = { buildDate:"2019-11-14T23:12:28+01:00", buildRevision: "c0eb30472e0031c0b83efc04f92184c56e6bf5b7" };
module.exports = { buildDate:"2019-12-03T22:31:20+01:00", buildRevision: "b0310e34e2c6f023cc8190310ff63d840157f6cc" };

View File

@@ -67,7 +67,7 @@ module.exports = function(filters, selectedColumns = 'notes.*') {
const params = [];
for (const filter of filters) {
if (['isarchived', 'orderby', 'limit'].includes(filter.name.toLowerCase())) {
if (['isarchived', 'in', 'orderby', 'limit'].includes(filter.name.toLowerCase())) {
continue; // these are not real filters
}

View File

@@ -311,6 +311,25 @@ async function findLogicIssues() {
}
});
await findAndFixIssues(`
SELECT notes.noteId
FROM notes
LEFT JOIN note_contents USING(noteId)
WHERE
note_contents.noteId IS NULL`,
async ({noteId}, autoFix) => {
if (autoFix) {
const note = await repository.getNote(noteId);
// empty string might be wrong choice for some note types (and protected notes) but it's a best guess
await note.setContent(note.isErased ? null : '');
logFix(`Note ${noteId} content was set to empty string since there was no corresponding row`);
}
else {
logError(`Note ${noteId} content row does not exist`);
}
});
await findAndFixIssues(`
SELECT noteId
FROM notes
@@ -321,6 +340,7 @@ async function findLogicIssues() {
async ({noteId}, autoFix) => {
if (autoFix) {
const note = await repository.getNote(noteId);
// empty string might be wrong choice for some note types (and protected notes) but it's a best guess
await note.setContent('');
logFix(`Note ${noteId} content was set to empty string since it was null even though it is not deleted`);
@@ -360,6 +380,25 @@ async function findLogicIssues() {
}
});
await findAndFixIssues(`
SELECT note_revisions.noteRevisionId
FROM note_revisions
LEFT JOIN note_revision_contents USING(noteRevisionId)
WHERE note_revision_contents.noteRevisionId IS NULL`,
async ({noteRevisionId}, autoFix) => {
if (autoFix) {
const noteRevision = await repository.getNoteRevision(noteRevisionId);
await noteRevision.setContent(null);
noteRevision.isErased = true;
await noteRevision.save();
logFix(`Note revision content ${noteRevisionId} was created and set to erased since it did not exist.`);
}
else {
logError(`Note revision content ${noteRevisionId} does not exist`);
}
});
await findAndFixIssues(`
SELECT noteRevisionId
FROM note_revisions
@@ -587,12 +626,31 @@ async function runAllChecks() {
return !unrecoveredConsistencyErrors;
}
async function showEntityStat(name, query) {
const map = await sql.getMap(query);
map[0] = map[0] || 0;
map[1] = map[1] || 0;
log.info(`${name} deleted: ${map[1]}, not deleted ${map[0]}`);
}
async function runDbDiagnostics() {
await showEntityStat("Notes", `SELECT isDeleted, count(noteId) FROM notes GROUP BY isDeleted`);
await showEntityStat("Note revisions", `SELECT isErased, count(noteRevisionId) FROM note_revisions GROUP BY isErased`);
await showEntityStat("Branches", `SELECT isDeleted, count(branchId) FROM branches GROUP BY isDeleted`);
await showEntityStat("Attributes", `SELECT isDeleted, count(attributeId) FROM attributes GROUP BY isDeleted`);
await showEntityStat("API tokens", `SELECT isDeleted, count(apiTokenId) FROM api_tokens GROUP BY isDeleted`);
}
async function runChecks() {
let elapsedTimeMs;
await syncMutexService.doExclusively(async () => {
const startTime = new Date();
await runDbDiagnostics();
await runAllChecks();
elapsedTimeMs = Date.now() - startTime.getTime();
@@ -624,7 +682,7 @@ sqlInit.dbReady.then(() => {
setInterval(cls.wrap(runChecks), 60 * 60 * 1000);
// kickoff checks soon after startup (to not block the initial load)
setTimeout(cls.wrap(runChecks), 10 * 1000);
setTimeout(cls.wrap(runChecks), 20 * 1000);
});
module.exports = {};

View File

@@ -56,6 +56,8 @@ async function checkContentHashes(otherHashes) {
if (hashes[key] !== otherHashes[key]) {
allChecksPassed = false;
log.info(`Content hash check for ${key} FAILED. Local is ${hashes[key]}, remote is ${otherHashes[key]}`);
if (key !== 'recent_notes') {
// let's not get alarmed about recent notes which get updated often and can cause failures in race conditions
ws.sendMessageToAllClients({type: 'sync-hash-check-failed'});

View File

@@ -81,7 +81,7 @@ eventService.subscribe(eventService.CHILD_NOTE_CREATED, async ({ parentNote, chi
async function processInverseRelations(entityName, entity, handler) {
if (entityName === 'attributes' && entity.type === 'relation') {
const note = await entity.getNote();
const attributes = (await note.getAttributes(entity.name)).filter(relation => relation.type === 'relation-definition');
const attributes = (await note.getOwnedAttributes(entity.name)).filter(relation => relation.type === 'relation-definition');
for (const attribute of attributes) {
const definition = attribute.value;

View File

@@ -12,9 +12,7 @@ const imageminGifLossy = require('imagemin-giflossy');
const jimp = require('jimp');
const imageType = require('image-type');
const sanitizeFilename = require('sanitize-filename');
const dateUtils = require('./date_utils');
const noteRevisionService = require('./note_revisions.js');
const NoteRevision = require("../entities/note_revision");
async function processImage(uploadBuffer, originalName, shrinkImageSwitch) {
const origImageFormat = imageType(uploadBuffer);

View File

@@ -1,9 +1,9 @@
const sax = require("sax");
const fileType = require('file-type');
const stream = require('stream');
const xml2js = require('xml2js');
const log = require("../log");
const utils = require("../utils");
const sql = require("../sql");
const noteService = require("../notes");
const imageService = require("../image");
const protectedSessionService = require('../protected_session');
@@ -12,7 +12,7 @@ const protectedSessionService = require('../protected_session');
function parseDate(text) {
// insert - and : to make it ISO format
text = text.substr(0, 4) + "-" + text.substr(4, 2) + "-" + text.substr(6, 2)
+ "T" + text.substr(9, 2) + ":" + text.substr(11, 2) + ":" + text.substr(13, 2) + "Z";
+ " " + text.substr(9, 2) + ":" + text.substr(11, 2) + ":" + text.substr(13, 2) + ".000Z";
return text;
}
@@ -22,8 +22,6 @@ let resource;
async function importEnex(taskContext, file, parentNote) {
const saxStream = sax.createStream(true);
const xmlBuilder = new xml2js.Builder({ headless: true });
const parser = new xml2js.Parser({ explicitArray: true });
const rootNoteTitle = file.originalname.toLowerCase().endsWith(".enex")
? file.originalname.substr(0, file.originalname.length - 5)
@@ -40,28 +38,20 @@ async function importEnex(taskContext, file, parentNote) {
// when we finish parsing. We use this to be sure that all saving has been finished before returning successfully.
const saveNotePromises = [];
async function parseXml(text) {
return new Promise(function(resolve, reject)
{
parser.parseString(text, function (err, result) {
if (err) {
reject(err);
}
else {
resolve(result);
}
});
});
}
function extractContent(content) {
const openingNoteIndex = content.indexOf('<en-note>');
function extractContent(enNote) {
// [] thing is workaround for https://github.com/Leonidas-from-XIV/node-xml2js/issues/484
let content = xmlBuilder.buildObject([enNote]);
if (openingNoteIndex !== -1) {
content = content.substr(openingNoteIndex + 9);
}
const endOfFirstTagIndex = content.indexOf('>');
const closingNoteIndex = content.lastIndexOf('</en-note>');
// strip the <0> and </0> tags
content = content.substr(endOfFirstTagIndex + 1, content.length - endOfFirstTagIndex - 5).trim();
if (closingNoteIndex !== -1) {
content = content.substr(0, closingNoteIndex);
}
content = content.trim();
// workaround for https://github.com/ckeditor/ckeditor5-list/issues/116
content = content.replace(/<li>\s+<div>/g, "<li>");
@@ -161,7 +151,7 @@ async function importEnex(taskContext, file, parentNote) {
} else if (currentTag === 'created') {
note.utcDateCreated = parseDate(text);
} else if (currentTag === 'updated') {
// updated is currently ignored since utcDateModified is updated automatically with each save
note.utcDateModified = parseDate(text);
} else if (currentTag === 'tag') {
note.attributes.push({
type: 'label',
@@ -198,14 +188,29 @@ async function importEnex(taskContext, file, parentNote) {
}
});
async function updateDates(noteId, utcDateCreated, utcDateModified) {
// it's difficult to force custom dateCreated and dateModified to Note entity so we do it post-creation with SQL
await sql.execute(`
UPDATE notes
SET dateCreated = ?,
utcDateCreated = ?,
dateModified = ?,
utcDateModified = ?
WHERE noteId = ?`,
[utcDateCreated, utcDateCreated, utcDateModified, utcDateModified, noteId]);
await sql.execute(`
UPDATE note_contents
SET utcDateModified = ?
WHERE noteId = ?`,
[utcDateModified, noteId]);
}
async function saveNote() {
// make a copy because stream continues with the next async call and note gets overwritten
let {title, content, attributes, resources, utcDateCreated} = note;
let {title, content, attributes, resources, utcDateCreated, utcDateModified} = note;
const xmlObject = await parseXml(content);
// following is workaround for this issue: https://github.com/Leonidas-from-XIV/node-xml2js/issues/484
content = extractContent(xmlObject['en-note']);
content = extractContent(content);
const noteEntity = (await noteService.createNote(rootNote.noteId, title, content, {
attributes,
@@ -215,6 +220,10 @@ async function importEnex(taskContext, file, parentNote) {
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
})).note;
utcDateCreated = utcDateCreated || noteEntity.utcDateCreated;
// sometime date modified is not present in ENEX, then use date created
utcDateModified = utcDateModified || utcDateCreated;
taskContext.increaseProgressCount();
let noteContent = await noteEntity.getContent();
@@ -238,6 +247,8 @@ async function importEnex(taskContext, file, parentNote) {
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
})).note;
await updateDates(resourceNote.noteId, utcDateCreated, utcDateModified);
taskContext.increaseProgressCount();
const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`;
@@ -249,7 +260,9 @@ async function importEnex(taskContext, file, parentNote) {
try {
const originalName = "image." + resource.mime.substr(6);
const {url} = await imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
const {url, note: imageNote} = await imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
await updateDates(imageNote.noteId, utcDateCreated, utcDateModified);
const imageLink = `<img src="${url}">`;
@@ -271,6 +284,10 @@ async function importEnex(taskContext, file, parentNote) {
// save updated content with links to files/images
await noteEntity.setContent(noteContent);
await noteService.scanForLinks(noteEntity.noteId);
await updateDates(noteEntity.noteId, utcDateCreated, utcDateModified);
}
saxStream.on("closetag", async tag => {

View File

@@ -258,7 +258,8 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
content = content.toString("UTF-8");
}
if ((noteMeta && noteMeta.format === 'markdown') || (!noteMeta && ['text/markdown', 'text/x-markdown'].includes(mime))) {
if ((noteMeta && noteMeta.format === 'markdown')
|| (!noteMeta && taskContext.data.textImportedAsText && ['text/markdown', 'text/x-markdown'].includes(mime))) {
const parsed = mdReader.parse(content);
content = mdWriter.render(parsed);
}

View File

@@ -43,9 +43,6 @@ async function migrate() {
try {
log.info("Attempting migration to version " + mig.dbVersion);
// needs to happen outside of the transaction (otherwise it's a NO-OP)
await sql.execute("PRAGMA foreign_keys = OFF");
await sql.transactional(async () => {
if (mig.type === 'sql') {
const migrationSql = fs.readFileSync(resourceDir.MIGRATIONS_DIR + "/" + mig.file).toString('utf8');
@@ -76,10 +73,6 @@ async function migrate() {
utils.crash();
}
finally {
// make sure foreign keys are enabled even if migration script disables them
await sql.execute("PRAGMA foreign_keys = ON");
}
}
if (await sqlInit.isDbUpToDate()) {

View File

@@ -255,6 +255,25 @@ function isArchived(noteId) {
return isNotePathArchived(notePath);
}
/**
* @param {string} noteId
* @param {string} ancestorNoteId
* @return {boolean} - true if given noteId has ancestorNoteId in any of its paths (even archived)
*/
function isInAncestor(noteId, ancestorNoteId) {
if (ancestorNoteId === noteId) { // special case
return true;
}
for (const parentNoteId of childToParent[noteId] || []) {
if (isInAncestor(parentNoteId, ancestorNoteId)) {
return true;
}
}
return false;
}
function getNoteTitleFromPath(notePath) {
const pathArr = notePath.split("/");
@@ -529,6 +548,7 @@ module.exports = {
getNoteTitleFromPath,
isAvailable,
isArchived,
isInAncestor,
load,
findSimilarNotes
};

View File

@@ -26,6 +26,10 @@ async function protectNoteRevisions(note) {
* @return {NoteRevision}
*/
async function createNoteRevision(note) {
if (await note.hasLabel("disableVersioning")) {
return;
}
const noteRevision = await new NoteRevision({
noteId: note.noteId,
// title and text should be decrypted now

View File

@@ -117,7 +117,7 @@ async function createNewNote(parentNoteId, noteData) {
isExpanded: !!noteData.isExpanded
}).save();
for (const attr of await parentNote.getAttributes()) {
for (const attr of await parentNote.getOwnedAttributes()) {
if (attr.name.startsWith("child:")) {
await new Attribute({
noteId: note.noteId,
@@ -308,8 +308,7 @@ async function saveLinks(note, content) {
}
async function saveNoteRevision(note) {
// files and images are immutable, they can't be updated
// but we don't even version titles which is probably not correct
// files and images are versioned separately
if (note.type === 'file' || note.type === 'image' || await note.hasLabel('disableVersioning')) {
return;
}
@@ -480,7 +479,7 @@ async function eraseDeletedNotes() {
SET content = NULL,
utcDateModified = '${utcNowDateTime}'
WHERE noteRevisionId IN
(SELECT noteRevisionId FROM note_revisions WHERE isErased = 0 AND noteId IN ((???)))`, noteIdsToErase);
(SELECT noteRevisionId FROM note_revisions WHERE isErased = 0 AND noteId IN (???))`, noteIdsToErase);
await sql.executeMany(`
UPDATE note_revisions
@@ -514,7 +513,7 @@ async function duplicateNote(noteId, parentNoteId) {
notePosition: origBranch ? origBranch.notePosition + 1 : null
}).save();
for (const attribute of await origNote.getAttributes()) {
for (const attribute of await origNote.getOwnedAttributes()) {
const attr = new Attribute(attribute);
attr.attributeId = undefined; // force creation of new attribute
attr.noteId = newNote.noteId;

View File

@@ -56,6 +56,8 @@ async function initNotSyncedOptions(initialized, startNotePath = 'root', opts =
}
const defaultOptions = [
{ name: 'noteRevisionSnapshotTimeInterval', value: '600', isSynced: true },
{ name: 'protectedSessionTimeout', value: '600', isSynced: true },
{ name: 'hoistedNoteId', value: 'root', isSynced: false },
{ name: 'zoomFactor', value: '1.0', isSynced: false },
{ name: 'leftPaneMinWidth', value: '350', isSynced: false },

View File

@@ -37,7 +37,7 @@ function isProtectedSessionAvailable() {
function decryptNotes(notes) {
for (const note of notes) {
if (note.isProtected) {
note.title = decrypt(note.title);
note.title = decryptString(note.title);
}
}
}

View File

@@ -35,6 +35,20 @@ async function searchForNoteIds(searchString) {
}
}
const isInFilters = filters.filter(filter => filter.name.toLowerCase() === 'in');
for (const isInFilter of isInFilters) {
if (isInFilter.operator === '=') {
noteIds = noteIds.filter(noteId => noteCacheService.isInAncestor(noteId, isInFilter.value));
}
else if (isInFilter.operator === '!=') {
noteIds = noteIds.filter(noteId => !noteCacheService.isInAncestor(noteId, isInFilter.value));
}
else {
throw new Error(`Unrecognized isIn operator ${isInFilter.operator}`);
}
}
const limitFilter = filters.find(filter => filter.name.toLowerCase() === 'limit');
if (limitFilter) {

View File

@@ -57,8 +57,6 @@ async function initDbConnection() {
return;
}
await sql.execute("PRAGMA foreign_keys = ON");
const currentDbVersion = await getDbVersion();
if (currentDbVersion > appInfo.dbVersion) {
@@ -175,9 +173,11 @@ async function isDbUpToDate() {
}
async function dbInitialized() {
await optionService.setOption('initialized', 'true');
if (!await isDbInitialized()) {
await optionService.setOption('initialized', 'true');
await initDbConnection();
await initDbConnection();
}
}
dbReady.then(async () => {

View File

@@ -4,7 +4,85 @@ const sql = require('./sql');
const repository = require('./repository');
const Branch = require('../entities/branch');
const syncTableService = require('./sync_table');
const log = require('./log');
const protectedSessionService = require('./protected_session');
const noteCacheService = require('./note_cache');
async function setCssClassesToNotes(notes) {
const noteIds = notes.map(note => note.noteId);
const noteMap = new Map(notes.map(note => [note.noteId, note]));
const templateClassLabels = await sql.getManyRows(`
SELECT
templAttr.noteId,
attr.name,
attr.value
FROM attributes templAttr
JOIN attributes attr ON attr.noteId = templAttr.value
WHERE
templAttr.isDeleted = 0
AND templAttr.type = 'relation'
AND templAttr.name = 'template'
AND templAttr.noteId IN (???)
AND attr.isDeleted = 0
AND attr.type = 'label'
AND attr.name IN ('cssClass', 'iconClass')`, noteIds);
const noteClassLabels = await sql.getManyRows(`
SELECT
noteId, name, value
FROM attributes
WHERE
isDeleted = 0
AND type = 'label'
AND name IN ('cssClass', 'iconClass')
AND noteId IN (???)`, noteIds);
// first template ones, then on the note itself so that note class label have priority
// over template ones for iconClass (which can be only one)
const allClassLabels = templateClassLabels.concat(noteClassLabels);
for (const label of allClassLabels) {
const note = noteMap.get(label.noteId);
if (note) {
if (label.name === 'cssClass') {
note.cssClass = note.cssClass ? `${note.cssClass} ${label.value}` : label.value;
} else if (label.name === 'iconClass') {
note.iconClass = label.value;
} else {
log.error(`Unrecognized label name ${label.name}`);
}
}
}
}
async function getNotes(noteIds) {
// we return also deleted notes which have been specifically asked for
const notes = await sql.getManyRows(`
SELECT
noteId,
title,
isProtected,
type,
mime,
isDeleted
FROM notes
WHERE noteId IN (???)`, noteIds);
await setCssClassesToNotes(notes);
protectedSessionService.decryptNotes(notes);
await noteCacheService.loadedPromise;
notes.forEach(note => {
note.isProtected = !!note.isProtected;
note.archived = noteCacheService.isArchived(note.noteId)
});
return notes;
}
async function validateParentChild(parentNoteId, childNoteId, branchId = null) {
if (childNoteId === 'root') {
@@ -171,8 +249,10 @@ async function setNoteToParent(noteId, prefix, parentNoteId) {
}
module.exports = {
getNotes,
validateParentChild,
getBranch,
sortNotesAlphabetically,
setNoteToParent
setNoteToParent,
setCssClassesToNotes
};

View File

@@ -1,48 +1,32 @@
const fs = require('fs');
const dataDir = require('../services/data_dir');
fs.unlinkSync(dataDir.DOCUMENT_PATH);
/**
* Usage: node src/tools/generate_document.js 1000
* will create 1000 new notes and some clones into a current document.db
*/
require('../entities/entity_constructor');
const optionService = require('../services/options');
const sqlInit = require('../services/sql_init');
const myScryptService = require('../services/my_scrypt');
const passwordEncryptionService = require('../services/password_encryption');
const utils = require('../services/utils');
const noteService = require('../services/notes');
const attributeService = require('../services/attributes');
const cls = require('../services/cls');
const cloningService = require('../services/cloning');
const loremIpsum = require('lorem-ipsum');
async function setUserNamePassword() {
const username = "test";
const password = "test";
await optionService.setOption('username', username);
await optionService.setOption('passwordVerificationSalt', utils.randomSecureToken(32));
await optionService.setOption('passwordDerivedKeySalt', utils.randomSecureToken(32));
const passwordVerificationKey = utils.toBase64(await myScryptService.getVerificationHash(password));
await optionService.setOption('passwordVerificationHash', passwordVerificationKey);
await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16));
await sqlInit.initDbConnection();
}
const loremIpsum = require('lorem-ipsum').loremIpsum;
const noteCount = parseInt(process.argv[2]);
if (!noteCount) {
console.error(`Please enter number of notes as program parameter.`);
process.exit(1);
}
const notes = ['root'];
function getRandomParentNoteId() {
function getRandomNoteId() {
const index = Math.floor(Math.random() * notes.length);
return notes[index];
}
async function start() {
await setUserNamePassword();
for (let i = 0; i < noteCount; i++) {
const title = loremIpsum({ count: 1, units: 'sentences', sentenceLowerBound: 1, sentenceUpperBound: 10 });
@@ -50,17 +34,17 @@ async function start() {
const content = loremIpsum({ count: paragraphCount, units: 'paragraphs', sentenceLowerBound: 1, sentenceUpperBound: 15,
paragraphLowerBound: 3, paragraphUpperBound: 10, format: 'html' });
const {note} = await noteService.createNote(getRandomParentNoteId(), title, content);
const {note} = await noteService.createNote(getRandomNoteId(), title, content);
console.log(`Created note ${i}: ${title}`);
notes.push(note.noteId);
}
// we'll create clones for 20% of notes
for (let i = 0; i < (noteCount / 50); i++) {
const noteIdToClone = getRandomParentNoteId();
const parentNoteId = getRandomParentNoteId();
// we'll create clones for 4% of notes
for (let i = 0; i < (noteCount / 25); i++) {
const noteIdToClone = getRandomNoteId();
const parentNoteId = getRandomNoteId();
const prefix = Math.random() > 0.8 ? "prefix" : null;
const result = await cloningService.cloneNoteToParent(noteIdToClone, parentNoteId, prefix);
@@ -68,6 +52,30 @@ async function start() {
console.log(`Cloning ${i}:`, result.success ? "succeeded" : "FAILED");
}
for (let i = 0; i < noteCount; i++) {
await attributeService.createAttribute({
noteId: getRandomNoteId(),
type: 'label',
name: 'label',
value: 'value',
isInheritable: Math.random() > 0.1 // 10% are inheritable
});
console.log(`Creating label ${i}`);
}
for (let i = 0; i < noteCount; i++) {
await attributeService.createAttribute({
noteId: getRandomNoteId(),
type: 'relation',
name: 'relation',
value: getRandomNoteId(),
isInheritable: Math.random() > 0.1 // 10% are inheritable
});
console.log(`Creating relation ${i}`);
}
process.exit(0);
}

View File

@@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="shortcut icon" href="favicon.ico">
<title>Trilium Notes</title>
</head>
<body class="desktop theme-<%= theme %>" style="--main-font-size: <%= mainFontSize %>%; --tree-font-size: <%= treeFontSize %>%; --detail-font-size: <%= detailFontSize %>%;">

View File

@@ -138,7 +138,6 @@
</div>
<script type="text/javascript">
const baseApiUrl = 'api/';
const glob = {
sourceId: ''
};