Compare commits

...

34 Commits

Author SHA1 Message Date
zadam
4ce4ac9584 release 0.40.3 2020-02-09 10:48:23 +01:00
zadam
88bd65c679 external links are also parsed and label is created for them, closes #851 2020-02-09 10:45:07 +01:00
zadam
9eab3026bb display advanced item in the tree context menu in the middle to avoid being hidden, closes #853 2020-02-09 10:15:35 +01:00
zadam
7abaedbf31 add possibility to change clipper parent to a fixed note instead of day notes, fixes #854 2020-02-09 10:12:02 +01:00
zadam
402718d293 return focus to the previously focused element after closing the dialog, fixes #861 2020-02-09 10:00:13 +01:00
zadam
990a84c202 Merge remote-tracking branch 'origin/master' 2020-02-09 08:38:47 +01:00
zadam
d8e181a828 Merge branch 'stable'
# Conflicts:
#	package.json
#	src/services/build.js
2020-02-09 08:38:29 +01:00
jasontan056
adb8caa8a2 Fix corner case preventing notes from being created before ckeditor is initialized (#849)
* Pass deleteId to deleteBranch in ensureNoteIsAbsentFromParent

* Add checks for whether window.cutToNote is defined.

* check ckEditor initialized.
2020-02-02 09:28:19 +01:00
zadam
7651c53363 release 0.40.2 2020-02-01 10:17:51 +01:00
zadam
0f25c8a95f autobook should not be active on the mobile interface 2020-02-01 10:17:03 +01:00
zadam
1a49894adf fix tree loading on mobile interface, closes #839 2020-02-01 10:04:18 +01:00
zadam
bd8c078fb9 Merge remote-tracking branch 'origin/master' 2020-02-01 09:30:14 +01:00
zadam
6e060b87b8 fix date parsing in local timezone, closes #845 2020-02-01 09:29:56 +01:00
jasontan056
2375b170ba Pass deleteId to deleteBranch in ensureNoteIsAbsentFromParent (#846) 2020-02-01 09:05:23 +01:00
zadam
828cce0d78 release 0.40.1 2020-01-19 15:45:06 +01:00
zadam
ab535bf147 fixes of the new CopyWithoutFormatting 2020-01-19 09:25:35 +01:00
Heniker
1876664dfb add hotkey to copy contents with line breaks, fixes #349 (#831) 2020-01-19 09:16:36 +01:00
zadam
1690248e24 migration script to fix contentLength = -1 in new notes 2020-01-19 09:08:33 +01:00
zadam
cbeb8ea17e fix setting contentLength 2020-01-19 09:03:26 +01:00
zadam
c9113ae752 Merge branch 'stable' 2020-01-18 09:24:39 +01:00
zadam
20fdeee048 better error handling for search notes 2020-01-13 19:35:06 +01:00
zadam
a79a063d17 release 0.40.0-beta 2020-01-11 09:54:31 +01:00
zadam
5e91b1b5e0 package updates 2020-01-11 09:50:05 +01:00
zadam
7877443fb4 Merge branch 'stable' 2020-01-11 09:41:42 +01:00
zadam
f3a2e2cbde Merge branch 'stable' 2020-01-08 21:39:17 +01:00
zadam
4c7c3105e8 Merge branch 'stable'
# Conflicts:
#	src/services/notes.js
2020-01-07 22:29:54 +01:00
zadam
1cd2711097 Merge branch 'stable' 2020-01-05 20:02:19 +01:00
zadam
54d89a9f47 allow configuring the time period before deleted notes are erased (and changed the default to 7 days) 2020-01-03 22:32:49 +01:00
zadam
1699646b39 fix undelete info messages 2020-01-03 21:32:41 +01:00
zadam
94a0a31f17 refactoring of tree handling to recommended fancytree 2020-01-03 21:15:45 +01:00
zadam
10219fb9dd Merge branch 'stable' 2020-01-03 20:13:13 +01:00
zadam
17b23d92ef delete/undelete fixes 2020-01-03 13:41:44 +01:00
zadam
14f3c783f2 undelete note WIP 2020-01-03 13:14:43 +01:00
zadam
c1d0a1e07b undelete note WIP 2020-01-03 10:48:36 +01:00
63 changed files with 898 additions and 432 deletions

View File

@@ -2,7 +2,7 @@
<dataSource name="document.db">
<database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.17">
<root id="1">
<ServerVersion>3.16.1</ServerVersion>
<ServerVersion>3.25.1</ServerVersion>
</root>
<schema id="2" parent="1" name="main">
<Current>1</Current>
@@ -57,6 +57,7 @@
<index id="24" parent="6" name="sqlite_autoindex_api_tokens_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>apiTokenId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="25" parent="6">
@@ -111,529 +112,573 @@
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="35" parent="7" name="hash">
<column id="35" parent="7" name="deleteId">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<DefaultExpression>NULL</DefaultExpression>
</column>
<column id="36" parent="7" name="hash">
<Position>11</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="36" parent="7" name="isInheritable">
<Position>11</Position>
<column id="37" parent="7" name="isInheritable">
<Position>12</Position>
<DataType>int|0s</DataType>
<DefaultExpression>0</DefaultExpression>
</column>
<index id="37" parent="7" name="sqlite_autoindex_attributes_1">
<index id="38" parent="7" name="sqlite_autoindex_attributes_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>attributeId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="38" parent="7" name="IDX_attributes_noteId_index">
<index id="39" parent="7" name="IDX_attributes_noteId_index">
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="39" parent="7" name="IDX_attributes_name_value">
<index id="40" parent="7" name="IDX_attributes_name_value">
<ColNames>name
value</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="40" parent="7" name="IDX_attributes_value_index">
<index id="41" parent="7" name="IDX_attributes_value_index">
<ColNames>value</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="41" parent="7">
<key id="42" parent="7">
<ColNames>attributeId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_attributes_1</UnderlyingIndexName>
</key>
<column id="42" parent="8" name="branchId">
<column id="43" parent="8" name="branchId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="43" parent="8" name="noteId">
<column id="44" parent="8" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="44" parent="8" name="parentNoteId">
<column id="45" parent="8" name="parentNoteId">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="45" parent="8" name="notePosition">
<column id="46" parent="8" name="notePosition">
<Position>4</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="46" parent="8" name="prefix">
<column id="47" parent="8" name="prefix">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="47" parent="8" name="isExpanded">
<column id="48" parent="8" name="isExpanded">
<Position>6</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="48" parent="8" name="isDeleted">
<column id="49" parent="8" name="isDeleted">
<Position>7</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="49" parent="8" name="utcDateModified">
<column id="50" parent="8" name="deleteId">
<Position>8</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>NULL</DefaultExpression>
</column>
<column id="50" parent="8" name="utcDateCreated">
<column id="51" parent="8" name="utcDateModified">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="51" parent="8" name="hash">
<column id="52" parent="8" name="utcDateCreated">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="53" parent="8" name="hash">
<Position>11</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="52" parent="8" name="sqlite_autoindex_branches_1">
<index id="54" parent="8" name="sqlite_autoindex_branches_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>branchId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="53" parent="8" name="IDX_branches_noteId_parentNoteId">
<index id="55" parent="8" name="IDX_branches_noteId_parentNoteId">
<ColNames>noteId
parentNoteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="54" parent="8" name="IDX_branches_parentNoteId">
<index id="56" parent="8" name="IDX_branches_parentNoteId">
<ColNames>parentNoteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="55" parent="8">
<key id="57" parent="8">
<ColNames>branchId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName>
</key>
<column id="56" parent="9" name="noteId">
<column id="58" parent="9" name="noteId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="57" parent="9" name="content">
<column id="59" parent="9" name="content">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<DefaultExpression>NULL</DefaultExpression>
</column>
<column id="58" parent="9" name="hash">
<column id="60" parent="9" name="hash">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="59" parent="9" name="utcDateModified">
<column id="61" parent="9" name="utcDateModified">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="60" parent="9" name="sqlite_autoindex_note_contents_1">
<index id="62" parent="9" name="sqlite_autoindex_note_contents_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="61" parent="9">
<key id="63" parent="9">
<ColNames>noteId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_note_contents_1</UnderlyingIndexName>
</key>
<column id="62" parent="10" name="noteRevisionId">
<column id="64" parent="10" name="noteRevisionId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="63" parent="10" name="content">
<column id="65" parent="10" name="content">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="64" parent="10" name="hash">
<column id="66" parent="10" name="hash">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<column id="65" parent="10" name="utcDateModified">
<column id="67" parent="10" name="utcDateModified">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="66" parent="10" name="sqlite_autoindex_note_revision_contents_1">
<index id="68" parent="10" name="sqlite_autoindex_note_revision_contents_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteRevisionId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="67" parent="10">
<key id="69" parent="10">
<ColNames>noteRevisionId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_note_revision_contents_1</UnderlyingIndexName>
</key>
<column id="68" parent="11" name="noteRevisionId">
<column id="70" parent="11" name="noteRevisionId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="69" parent="11" name="noteId">
<column id="71" parent="11" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="70" parent="11" name="title">
<column id="72" parent="11" name="title">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="71" parent="11" name="contentLength">
<column id="73" parent="11" name="contentLength">
<Position>4</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="72" parent="11" name="isErased">
<column id="74" parent="11" name="isErased">
<Position>5</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="73" parent="11" name="isProtected">
<column id="75" parent="11" name="isProtected">
<Position>6</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="74" parent="11" name="utcDateLastEdited">
<column id="76" parent="11" name="utcDateLastEdited">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="75" parent="11" name="utcDateCreated">
<column id="77" parent="11" name="utcDateCreated">
<Position>8</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="76" parent="11" name="utcDateModified">
<column id="78" parent="11" name="utcDateModified">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="77" parent="11" name="dateLastEdited">
<column id="79" parent="11" name="dateLastEdited">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="78" parent="11" name="dateCreated">
<column id="80" parent="11" name="dateCreated">
<Position>11</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="79" parent="11" name="type">
<column id="81" parent="11" name="type">
<Position>12</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<column id="80" parent="11" name="mime">
<column id="82" parent="11" name="mime">
<Position>13</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<column id="81" parent="11" name="hash">
<column id="83" parent="11" name="hash">
<Position>14</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<index id="82" parent="11" name="sqlite_autoindex_note_revisions_1">
<index id="84" parent="11" name="sqlite_autoindex_note_revisions_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteRevisionId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="83" parent="11" name="IDX_note_revisions_noteId">
<index id="85" parent="11" name="IDX_note_revisions_noteId">
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="84" parent="11" name="IDX_note_revisions_utcDateLastEdited">
<index id="86" parent="11" name="IDX_note_revisions_utcDateLastEdited">
<ColNames>utcDateLastEdited</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="85" parent="11" name="IDX_note_revisions_utcDateCreated">
<index id="87" parent="11" name="IDX_note_revisions_utcDateCreated">
<ColNames>utcDateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="86" parent="11" name="IDX_note_revisions_dateLastEdited">
<index id="88" parent="11" name="IDX_note_revisions_dateLastEdited">
<ColNames>dateLastEdited</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="87" parent="11" name="IDX_note_revisions_dateCreated">
<index id="89" parent="11" name="IDX_note_revisions_dateCreated">
<ColNames>dateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="88" parent="11">
<key id="90" parent="11">
<ColNames>noteRevisionId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName>
</key>
<column id="89" parent="12" name="noteId">
<column id="91" parent="12" name="noteId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="90" parent="12" name="title">
<column id="92" parent="12" name="title">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;note&quot;</DefaultExpression>
</column>
<column id="91" parent="12" name="contentLength">
<column id="93" parent="12" name="contentLength">
<Position>3</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="92" parent="12" name="isProtected">
<column id="94" parent="12" name="isProtected">
<Position>4</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="93" parent="12" name="type">
<column id="95" parent="12" name="type">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;text&apos;</DefaultExpression>
</column>
<column id="94" parent="12" name="mime">
<column id="96" parent="12" name="mime">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;text/html&apos;</DefaultExpression>
</column>
<column id="95" parent="12" name="hash">
<column id="97" parent="12" name="hash">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="96" parent="12" name="isDeleted">
<column id="98" parent="12" name="isDeleted">
<Position>8</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="97" parent="12" name="isErased">
<column id="99" parent="12" name="deleteId">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<DefaultExpression>NULL</DefaultExpression>
</column>
<column id="100" parent="12" name="isErased">
<Position>10</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="98" parent="12" name="dateCreated">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="99" parent="12" name="dateModified">
<column id="101" parent="12" name="dateCreated">
<Position>11</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="100" parent="12" name="utcDateCreated">
<column id="102" parent="12" name="dateModified">
<Position>12</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="101" parent="12" name="utcDateModified">
<column id="103" parent="12" name="utcDateCreated">
<Position>13</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="102" parent="12" name="sqlite_autoindex_notes_1">
<column id="104" parent="12" name="utcDateModified">
<Position>14</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="105" parent="12" name="sqlite_autoindex_notes_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="103" parent="12" name="IDX_notes_title">
<index id="106" parent="12" name="IDX_notes_title">
<ColNames>title</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="104" parent="12" name="IDX_notes_type">
<index id="107" parent="12" name="IDX_notes_type">
<ColNames>type</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="105" parent="12" name="IDX_notes_isDeleted">
<index id="108" parent="12" name="IDX_notes_isDeleted">
<ColNames>isDeleted</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="106" parent="12" name="IDX_notes_dateCreated">
<index id="109" parent="12" name="IDX_notes_dateCreated">
<ColNames>dateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="107" parent="12" name="IDX_notes_dateModified">
<index id="110" parent="12" name="IDX_notes_dateModified">
<ColNames>dateModified</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="108" parent="12" name="IDX_notes_utcDateCreated">
<index id="111" parent="12" name="IDX_notes_utcDateCreated">
<ColNames>utcDateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="109" parent="12" name="IDX_notes_utcDateModified">
<index id="112" parent="12" name="IDX_notes_utcDateModified">
<ColNames>utcDateModified</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="110" parent="12">
<key id="113" parent="12">
<ColNames>noteId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName>
</key>
<column id="111" parent="13" name="name">
<column id="114" parent="13" name="name">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="112" parent="13" name="value">
<column id="115" parent="13" name="value">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="113" parent="13" name="isSynced">
<column id="116" parent="13" name="isSynced">
<Position>3</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="114" parent="13" name="hash">
<column id="117" parent="13" name="hash">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="115" parent="13" name="utcDateCreated">
<column id="118" parent="13" name="utcDateCreated">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="116" parent="13" name="utcDateModified">
<column id="119" parent="13" name="utcDateModified">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="117" parent="13" name="sqlite_autoindex_options_1">
<index id="120" parent="13" name="sqlite_autoindex_options_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>name</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="118" parent="13">
<key id="121" parent="13">
<ColNames>name</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName>
</key>
<column id="119" parent="14" name="noteId">
<column id="122" parent="14" name="noteId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="120" parent="14" name="notePath">
<column id="123" parent="14" name="notePath">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="121" parent="14" name="hash">
<column id="124" parent="14" name="hash">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="122" parent="14" name="utcDateCreated">
<column id="125" parent="14" name="utcDateCreated">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="123" parent="14" name="isDeleted">
<column id="126" parent="14" name="isDeleted">
<Position>5</Position>
<DataType>INT|0s</DataType>
</column>
<index id="124" parent="14" name="sqlite_autoindex_recent_notes_1">
<index id="127" parent="14" name="sqlite_autoindex_recent_notes_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="125" parent="14">
<key id="128" parent="14">
<ColNames>noteId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName>
</key>
<column id="126" parent="15" name="sourceId">
<column id="129" parent="15" name="sourceId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="127" parent="15" name="utcDateCreated">
<column id="130" parent="15" name="utcDateCreated">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="128" parent="15" name="sqlite_autoindex_source_ids_1">
<index id="131" parent="15" name="sqlite_autoindex_source_ids_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>sourceId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="129" parent="15" name="IDX_source_ids_utcDateCreated">
<index id="132" parent="15" name="IDX_source_ids_utcDateCreated">
<ColNames>utcDateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="130" parent="15">
<key id="133" parent="15">
<ColNames>sourceId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName>
</key>
<column id="131" parent="16" name="type">
<column id="134" parent="16" name="type">
<Position>1</Position>
<DataType>text|0s</DataType>
</column>
<column id="132" parent="16" name="name">
<column id="135" parent="16" name="name">
<Position>2</Position>
<DataType>text|0s</DataType>
</column>
<column id="133" parent="16" name="tbl_name">
<column id="136" parent="16" name="tbl_name">
<Position>3</Position>
<DataType>text|0s</DataType>
</column>
<column id="134" parent="16" name="rootpage">
<column id="137" parent="16" name="rootpage">
<Position>4</Position>
<DataType>integer|0s</DataType>
<DataType>int|0s</DataType>
</column>
<column id="135" parent="16" name="sql">
<column id="138" parent="16" name="sql">
<Position>5</Position>
<DataType>text|0s</DataType>
</column>
<column id="136" parent="17" name="name">
<column id="139" parent="17" name="name">
<Position>1</Position>
</column>
<column id="137" parent="17" name="seq">
<column id="140" parent="17" name="seq">
<Position>2</Position>
</column>
<column id="138" parent="18" name="id">
<column id="141" parent="18" name="id">
<Position>1</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="139" parent="18" name="entityName">
<column id="142" parent="18" name="entityName">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="140" parent="18" name="entityId">
<column id="143" parent="18" name="entityId">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="141" parent="18" name="sourceId">
<column id="144" parent="18" name="sourceId">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="142" parent="18" name="utcSyncDate">
<column id="145" parent="18" name="utcSyncDate">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="143" parent="18" name="IDX_sync_entityName_entityId">
<index id="146" parent="18" name="IDX_sync_entityName_entityId">
<ColNames>entityName
entityId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="144" parent="18" name="IDX_sync_utcSyncDate">
<index id="147" parent="18" name="IDX_sync_utcSyncDate">
<ColNames>utcSyncDate</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="145" parent="18">
<key id="148" parent="18">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>

View File

@@ -0,0 +1,81 @@
CREATE TABLE IF NOT EXISTS "notes_mig" (
`noteId` TEXT NOT NULL,
`title` TEXT NOT NULL DEFAULT "note",
`contentLength` INT NOT NULL,
`isProtected` INT NOT NULL DEFAULT 0,
`type` TEXT NOT NULL DEFAULT 'text',
`mime` TEXT NOT NULL DEFAULT 'text/html',
`hash` TEXT DEFAULT "" NOT NULL,
`isDeleted` INT NOT NULL DEFAULT 0,
`deleteId` TEXT DEFAULT NULL,
`isErased` INT NOT NULL DEFAULT 0,
`dateCreated` TEXT NOT NULL,
`dateModified` TEXT NOT NULL,
`utcDateCreated` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL,
PRIMARY KEY(`noteId`));
INSERT INTO notes_mig (noteId, title, contentLength, isProtected, type, mime, hash, isDeleted, isErased, dateCreated, dateModified, utcDateCreated, utcDateModified)
SELECT noteId, title, -1, isProtected, type, mime, hash, isDeleted, isErased, dateCreated, dateModified, utcDateCreated, utcDateModified FROM notes;
DROP TABLE notes;
ALTER TABLE notes_mig RENAME TO notes;
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (`isDeleted`);
CREATE INDEX `IDX_notes_title` ON `notes` (`title`);
CREATE INDEX `IDX_notes_type` ON `notes` (`type`);
CREATE INDEX `IDX_notes_dateCreated` ON `notes` (`dateCreated`);
CREATE INDEX `IDX_notes_dateModified` ON `notes` (`dateModified`);
CREATE INDEX `IDX_notes_utcDateModified` ON `notes` (`utcDateModified`);
CREATE INDEX `IDX_notes_utcDateCreated` ON `notes` (`utcDateCreated`);
CREATE TABLE IF NOT EXISTS "branches_mig" (
`branchId` TEXT NOT NULL,
`noteId` TEXT NOT NULL,
`parentNoteId` TEXT NOT NULL,
`notePosition` INTEGER NOT NULL,
`prefix` TEXT,
`isExpanded` INTEGER NOT NULL DEFAULT 0,
`isDeleted` INTEGER NOT NULL DEFAULT 0,
`deleteId` TEXT DEFAULT NULL,
`utcDateModified` TEXT NOT NULL,
utcDateCreated TEXT NOT NULL,
hash TEXT DEFAULT "" NOT NULL,
PRIMARY KEY(`branchId`));
INSERT INTO branches_mig (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, utcDateModified, utcDateCreated, hash)
SELECT branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, utcDateModified, utcDateCreated, hash FROM branches;
DROP TABLE branches;
ALTER TABLE branches_mig RENAME TO branches;
CREATE INDEX `IDX_branches_noteId_parentNoteId` ON `branches` (`noteId`,`parentNoteId`);
CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId);
CREATE TABLE IF NOT EXISTS "attributes_mig"
(
attributeId TEXT not null primary key,
noteId TEXT not null,
type TEXT not null,
name TEXT not null,
value TEXT default '' not null,
position INT default 0 not null,
utcDateCreated TEXT not null,
utcDateModified TEXT not null,
isDeleted INT not null,
`deleteId` TEXT DEFAULT NULL,
hash TEXT default "" not null,
isInheritable int DEFAULT 0 NULL);
INSERT INTO attributes_mig (attributeId, noteId, type, name, value, position, utcDateCreated, utcDateModified, isDeleted, hash, isInheritable)
SELECT attributeId, noteId, type, name, value, position, utcDateCreated, utcDateModified, isDeleted, hash, isInheritable FROM attributes;
DROP TABLE attributes;
ALTER TABLE attributes_mig RENAME TO attributes;
CREATE INDEX IDX_attributes_name_value
on attributes (name, value);
CREATE INDEX IDX_attributes_noteId_index
on attributes (noteId);
CREATE INDEX IDX_attributes_value_index
on attributes (value);

View File

@@ -0,0 +1 @@
UPDATE notes SET contentLength = COALESCE((SELECT COALESCE(LENGTH(content), 0) FROM note_contents WHERE note_contents.noteId = notes.noteId), -1);

View File

@@ -27,19 +27,6 @@ CREATE TABLE IF NOT EXISTS "options"
utcDateCreated TEXT not null,
utcDateModified TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS "attributes"
(
attributeId TEXT not null primary key,
noteId TEXT not null,
type TEXT not null,
name TEXT not null,
value TEXT default '' not null,
position INT default 0 not null,
utcDateCreated TEXT not null,
utcDateModified TEXT not null,
isDeleted INT not null,
hash TEXT default "" not null,
isInheritable int DEFAULT 0 NULL);
CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` (
`entityName`,
`entityId`
@@ -47,12 +34,6 @@ CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` (
CREATE INDEX `IDX_sync_utcSyncDate` ON `sync` (
`utcSyncDate`
);
CREATE INDEX IDX_attributes_name_value
on attributes (name, value);
CREATE INDEX IDX_attributes_noteId_index
on attributes (noteId);
CREATE INDEX IDX_attributes_value_index
on attributes (value);
CREATE TABLE IF NOT EXISTS "note_contents" (
`noteId` TEXT NOT NULL,
`content` TEXT NULL DEFAULT NULL,
@@ -68,46 +49,10 @@ CREATE TABLE recent_notes
utcDateCreated TEXT not null,
isDeleted INT
);
CREATE TABLE IF NOT EXISTS "branches" (
`branchId` TEXT NOT NULL,
`noteId` TEXT NOT NULL,
`parentNoteId` TEXT NOT NULL,
`notePosition` INTEGER NOT NULL,
`prefix` TEXT,
`isExpanded` INTEGER NOT NULL DEFAULT 0,
`isDeleted` INTEGER NOT NULL DEFAULT 0,
`utcDateModified` TEXT NOT NULL,
utcDateCreated TEXT NOT NULL,
hash TEXT DEFAULT "" NOT NULL,
PRIMARY KEY(`branchId`));
CREATE INDEX `IDX_branches_noteId_parentNoteId` ON `branches` (`noteId`,`parentNoteId`);
CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId);
CREATE TABLE IF NOT EXISTS "note_revision_contents" (`noteRevisionId` TEXT NOT NULL PRIMARY KEY,
`content` TEXT,
hash TEXT DEFAULT '' NOT NULL,
`utcDateModified` TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS "notes" (
`noteId` TEXT NOT NULL,
`title` TEXT NOT NULL DEFAULT "note",
`contentLength` INT NOT NULL,
`isProtected` INT NOT NULL DEFAULT 0,
`type` TEXT NOT NULL DEFAULT 'text',
`mime` TEXT NOT NULL DEFAULT 'text/html',
`hash` TEXT DEFAULT "" NOT NULL,
`isDeleted` INT NOT NULL DEFAULT 0,
`isErased` INT NOT NULL DEFAULT 0,
`dateCreated` TEXT NOT NULL,
`dateModified` TEXT NOT NULL,
`utcDateCreated` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL,
PRIMARY KEY(`noteId`));
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (`isDeleted`);
CREATE INDEX `IDX_notes_title` ON `notes` (`title`);
CREATE INDEX `IDX_notes_type` ON `notes` (`type`);
CREATE INDEX `IDX_notes_dateCreated` ON `notes` (`dateCreated`);
CREATE INDEX `IDX_notes_dateModified` ON `notes` (`dateModified`);
CREATE INDEX `IDX_notes_utcDateModified` ON `notes` (`utcDateModified`);
CREATE INDEX `IDX_notes_utcDateCreated` ON `notes` (`utcDateCreated`);
CREATE TABLE IF NOT EXISTS "note_revisions" (`noteRevisionId` TEXT NOT NULL PRIMARY KEY,
`noteId` TEXT NOT NULL,
`title` TEXT,
@@ -127,3 +72,61 @@ CREATE INDEX `IDX_note_revisions_utcDateCreated` ON `note_revisions` (`utcDateCr
CREATE INDEX `IDX_note_revisions_utcDateLastEdited` ON `note_revisions` (`utcDateLastEdited`);
CREATE INDEX `IDX_note_revisions_dateCreated` ON `note_revisions` (`dateCreated`);
CREATE INDEX `IDX_note_revisions_dateLastEdited` ON `note_revisions` (`dateLastEdited`);
CREATE TABLE IF NOT EXISTS "notes" (
`noteId` TEXT NOT NULL,
`title` TEXT NOT NULL DEFAULT "note",
`contentLength` INT NOT NULL,
`isProtected` INT NOT NULL DEFAULT 0,
`type` TEXT NOT NULL DEFAULT 'text',
`mime` TEXT NOT NULL DEFAULT 'text/html',
`hash` TEXT DEFAULT "" NOT NULL,
`isDeleted` INT NOT NULL DEFAULT 0,
`deleteId` TEXT DEFAULT NULL,
`isErased` INT NOT NULL DEFAULT 0,
`dateCreated` TEXT NOT NULL,
`dateModified` TEXT NOT NULL,
`utcDateCreated` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL,
PRIMARY KEY(`noteId`));
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (`isDeleted`);
CREATE INDEX `IDX_notes_title` ON `notes` (`title`);
CREATE INDEX `IDX_notes_type` ON `notes` (`type`);
CREATE INDEX `IDX_notes_dateCreated` ON `notes` (`dateCreated`);
CREATE INDEX `IDX_notes_dateModified` ON `notes` (`dateModified`);
CREATE INDEX `IDX_notes_utcDateModified` ON `notes` (`utcDateModified`);
CREATE INDEX `IDX_notes_utcDateCreated` ON `notes` (`utcDateCreated`);
CREATE TABLE IF NOT EXISTS "branches" (
`branchId` TEXT NOT NULL,
`noteId` TEXT NOT NULL,
`parentNoteId` TEXT NOT NULL,
`notePosition` INTEGER NOT NULL,
`prefix` TEXT,
`isExpanded` INTEGER NOT NULL DEFAULT 0,
`isDeleted` INTEGER NOT NULL DEFAULT 0,
`deleteId` TEXT DEFAULT NULL,
`utcDateModified` TEXT NOT NULL,
utcDateCreated TEXT NOT NULL,
hash TEXT DEFAULT "" NOT NULL,
PRIMARY KEY(`branchId`));
CREATE INDEX `IDX_branches_noteId_parentNoteId` ON `branches` (`noteId`,`parentNoteId`);
CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId);
CREATE TABLE IF NOT EXISTS "attributes"
(
attributeId TEXT not null primary key,
noteId TEXT not null,
type TEXT not null,
name TEXT not null,
value TEXT default '' not null,
position INT default 0 not null,
utcDateCreated TEXT not null,
utcDateModified TEXT not null,
isDeleted INT not null,
`deleteId` TEXT DEFAULT NULL,
hash TEXT default "" not null,
isInheritable int DEFAULT 0 NULL);
CREATE INDEX IDX_attributes_name_value
on attributes (name, value);
CREATE INDEX IDX_attributes_noteId_index
on attributes (noteId);
CREATE INDEX IDX_attributes_value_index
on attributes (value);

115
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "trilium",
"version": "0.39.5",
"version": "0.40.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -552,6 +552,11 @@
"defer-to-connect": "^1.0.1"
}
},
"@tokenizer/token": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.1.1.tgz",
"integrity": "sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w=="
},
"@types/debug": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz",
@@ -975,12 +980,11 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
},
"axios": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
"integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.1.tgz",
"integrity": "sha512-Yl+7nfreYKaLRvAvjNPkvfjnQHJM1yLBY3zhqAwcJSwR/6ETkanUgylgtIvkvz0xJ+p/vZuNw8X7Hnb7Whsbpw==",
"requires": {
"follow-redirects": "1.5.10",
"is-buffer": "^2.0.2"
"follow-redirects": "1.5.10"
}
},
"bagpipe": {
@@ -1771,13 +1775,13 @@
}
},
"commonmark": {
"version": "0.29.0",
"resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.29.0.tgz",
"integrity": "sha512-Wc3kvAIm0EK85pHsM95Fev31wEN6/zQpwd2qcLDL8psjHRoUFvUeGHevIJAdToWUuFoX8WI/gmeDauqy32xgJQ==",
"version": "0.29.1",
"resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.29.1.tgz",
"integrity": "sha512-DafPdNYFXoEhsSiR4O+dJ45UJBfDL4cBTks4B+agKiaWt7qjG0bIhg5xuCE0RqU71ikJcBIf4/sRHh9vYQVF8Q==",
"requires": {
"entities": "~ 1.1.1",
"mdurl": "~ 1.0.1",
"minimist": "~ 1.2.0",
"entities": "~1.1.1",
"mdurl": "~1.0.1",
"minimist": "~1.2.0",
"string.prototype.repeat": "^0.2.0"
}
},
@@ -2081,9 +2085,9 @@
"integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI="
},
"dayjs": {
"version": "1.8.18",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.18.tgz",
"integrity": "sha512-JBMJZghNK8TtuoPnKNIzW9xavVVigld/zmZNpZSyQbkb2Opp55YIfZUpE4OEqPF/iyUVQTKcn1bC2HtC8B7s3g=="
"version": "1.8.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.19.tgz",
"integrity": "sha512-7kqOoj3oQSmqbvtvGFLU5iYqies+SqUiEGNT0UtUPPxcPYgY1BrkXR0Cq2R9HYSimBXN+xHkEN4Hi399W+Ovlg=="
},
"debug": {
"version": "4.1.1",
@@ -3619,9 +3623,15 @@
}
},
"file-type": {
"version": "12.4.2",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz",
"integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg=="
"version": "13.0.3",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-13.0.3.tgz",
"integrity": "sha512-fe3+9mnMLbpP8nRQm/cJgehypv375SOFOXFdLYyYe7hWxdWgS5hkDpWnld5At/vvfVEN6rVqKA3EOKMP03tJGg==",
"requires": {
"readable-web-to-node-stream": "^2.0.0",
"strtok3": "^5.0.1",
"token-types": "^2.0.0",
"typedarray-to-buffer": "^3.1.5"
}
},
"filename-regex": {
"version": "2.0.1",
@@ -4772,6 +4782,13 @@
"make-dir": "^3.0.0",
"p-pipe": "^3.0.0",
"replace-ext": "^1.0.0"
},
"dependencies": {
"file-type": {
"version": "12.4.2",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz",
"integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg=="
}
}
},
"imagemin-giflossy": {
@@ -4969,11 +4986,6 @@
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
},
"is-buffer": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
},
"is-bzip2": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-bzip2/-/is-bzip2-1.0.0.tgz",
@@ -5973,17 +5985,17 @@
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
},
"mime-types": {
"version": "2.1.25",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz",
"integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==",
"version": "2.1.26",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
"integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
"requires": {
"mime-db": "1.42.0"
"mime-db": "1.43.0"
},
"dependencies": {
"mime-db": {
"version": "1.42.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz",
"integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ=="
"version": "1.43.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
"integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ=="
}
}
},
@@ -7463,6 +7475,11 @@
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
},
"peek-readable": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-3.1.0.tgz",
"integrity": "sha512-KGuODSTV6hcgdZvDrIDBUkN0utcAVj1LL7FfGbM0viKTtCHmtZcuEJ+lGqsp0fTFkGqesdtemV2yUSMeyy3ddA=="
},
"pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
@@ -8436,6 +8453,11 @@
"util-deprecate": "~1.0.1"
}
},
"readable-web-to-node-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-2.0.0.tgz",
"integrity": "sha512-+oZJurc4hXpaaqsN68GoZGQAQIA3qr09Or4fqEsargABnbe5Aau8hFn6ISVleT3cpY/0n/8drn7huyyEvTbghA=="
},
"redent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
@@ -9162,9 +9184,9 @@
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
},
"string-similarity": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-3.0.0.tgz",
"integrity": "sha512-7kS7LyTp56OqOI2BDWQNVnLX/rCxIQn+/5M0op1WV6P8Xx6TZNdajpuqQdiJ7Xx+p1C5CsWMvdiBp9ApMhxzEQ=="
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.1.tgz",
"integrity": "sha512-v36MJzloekKVvKAsYi6O/qpn2mIuvwEFIT9Gx3yg4spkNjXYsk7yxc37g4ZTyMVIBvt/9PZGxnqEtme8XHK+Mw=="
},
"string-width": {
"version": "1.0.2",
@@ -9271,6 +9293,16 @@
"escape-string-regexp": "^1.0.2"
}
},
"strtok3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-5.0.1.tgz",
"integrity": "sha512-AWliiIjyb87onqO8pM+1Hozm+PPcR4YYIWbFUT5OKQ+tOMwgdT8HwJd/IS8v3/gKdAtE5aE2p3FhcWqryuZPLQ==",
"requires": {
"@tokenizer/token": "^0.1.0",
"debug": "^4.1.1",
"peek-readable": "^3.1.0"
}
},
"sum-up": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.3.tgz",
@@ -9560,6 +9592,15 @@
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
"token-types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-2.0.0.tgz",
"integrity": "sha512-WWvu8sGK8/ZmGusekZJJ5NM6rRVTTDO7/bahz4NGiSDb/XsmdYBn6a1N/bymUHuWYTWeuLUg98wUzvE4jPdCZw==",
"requires": {
"@tokenizer/token": "^0.1.0",
"ieee754": "^1.1.13"
}
},
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
@@ -9673,6 +9714,14 @@
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
"requires": {
"is-typedarray": "^1.0.0"
}
},
"uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.39.6",
"version": "0.40.3",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -21,13 +21,13 @@
},
"dependencies": {
"async-mutex": "0.1.4",
"axios": "0.19.0",
"axios": "0.19.1",
"body-parser": "1.19.0",
"cls-hooked": "4.2.2",
"commonmark": "0.29.0",
"commonmark": "0.29.1",
"cookie-parser": "1.4.4",
"csurf": "1.10.0",
"dayjs": "1.8.18",
"dayjs": "1.8.19",
"debug": "4.1.1",
"ejs": "2.7.4",
"electron-debug": "3.0.1",
@@ -37,7 +37,7 @@
"electron-window-state": "5.0.3",
"express": "4.17.1",
"express-session": "1.17.0",
"file-type": "12.4.2",
"file-type": "13.0.3",
"fs-extra": "8.1.0",
"helmet": "3.21.2",
"html": "1.0.0",
@@ -51,7 +51,7 @@
"imagemin-pngquant": "8.0.0",
"ini": "1.3.5",
"jimp": "0.9.3",
"mime-types": "2.1.25",
"mime-types": "2.1.26",
"moment": "2.24.0",
"multer": "1.4.2",
"node-abi": "2.13.0",
@@ -69,7 +69,7 @@
"simple-node-logger": "18.12.23",
"sqlite": "3.0.3",
"sqlite3": "4.1.1",
"string-similarity": "3.0.0",
"string-similarity": "4.0.1",
"tar-stream": "2.1.0",
"turndown": "5.0.3",
"turndown-plugin-gfm": "1.0.2",

View File

@@ -16,6 +16,7 @@ const sql = require('../services/sql');
* @property {int} position
* @property {boolean} isInheritable
* @property {boolean} isDeleted
* @property {string|null} deleteId - ID identifying delete transaction
* @property {string} utcDateCreated
* @property {string} utcDateModified
*

View File

@@ -16,6 +16,7 @@ const sql = require('../services/sql');
* @property {string} prefix
* @property {boolean} isExpanded
* @property {boolean} isDeleted
* @property {string|null} deleteId - ID identifying delete transaction
* @property {string} utcDateModified
* @property {string} utcDateCreated
*
@@ -25,7 +26,7 @@ class Branch extends Entity {
static get entityName() { return "branches"; }
static get primaryKeyName() { return "branchId"; }
// notePosition is not part of hash because it would produce a lot of updates in case of reordering
static get hashedProperties() { return ["branchId", "noteId", "parentNoteId", "isDeleted", "prefix"]; }
static get hashedProperties() { return ["branchId", "noteId", "parentNoteId", "isDeleted", "deleteId", "prefix"]; }
constructor(row = {}) {
super(row);

View File

@@ -24,6 +24,7 @@ const RELATION_DEFINITION = 'relation-definition';
* @property {int} contentLength - length of content
* @property {boolean} isProtected - true if note is protected
* @property {boolean} isDeleted - true if note is deleted
* @property {string|null} deleteId - ID identifying delete transaction
* @property {boolean} isErased - true if note's content is erased after it has been deleted
* @property {string} dateCreated - local date time (with offset)
* @property {string} dateModified - local date time (with offset)
@@ -35,7 +36,7 @@ const RELATION_DEFINITION = 'relation-definition';
class Note extends Entity {
static get entityName() { return "notes"; }
static get primaryKeyName() { return "noteId"; }
static get hashedProperties() { return ["noteId", "title", "type", "isProtected", "isDeleted"]; }
static get hashedProperties() { return ["noteId", "title", "type", "isProtected", "isDeleted", "deleteId"]; }
/**
* @param row - object containing database row from "notes" table
@@ -109,6 +110,10 @@ class Note extends Entity {
async getJsonContent() {
const content = await this.getContent();
if (!content || !content.trim()) {
return null;
}
return JSON.parse(content);
}
@@ -118,9 +123,11 @@ class Note extends Entity {
throw new Error(`Cannot set null content to note ${this.noteId}`);
}
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
// force updating note itself so that dateModified is represented correctly even for the content
this.forcedChange = true;
this.contentLength = content.length;
this.contentLength = content.byteLength;
await this.save();
this.content = content;
@@ -129,7 +136,7 @@ class Note extends Entity {
noteId: this.noteId,
content: content,
utcDateModified: dateUtils.utcNowDateTime(),
hash: utils.hash(this.noteId + "|" + content)
hash: utils.hash(this.noteId + "|" + content.toString())
};
if (this.isProtected) {
@@ -804,8 +811,10 @@ class Note extends Entity {
FROM attributes
WHERE noteId = ? AND
isDeleted = 0 AND
type = 'relation' AND
name IN ('internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink')`, [this.noteId]);
((type = 'relation' AND
name IN ('internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink'))
OR
(type = 'label' AND name = 'externalLink'))`, [this.noteId]);
}
/**

View File

@@ -10,8 +10,6 @@ const $buildRevision = $("#build-revision");
const $dataDirectory = $("#data-directory");
export async function showDialog() {
utils.closeActiveDialog();
const appInfo = await server.get('app-info');
$appVersion.text(appInfo.appVersion);
@@ -22,7 +20,5 @@ export async function showDialog() {
$buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision);
$dataDirectory.text(appInfo.dataDirectory);
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
}

View File

@@ -11,13 +11,9 @@ const $linkTitle = $("#link-title");
const $addLinkTitleFormGroup = $("#add-link-title-form-group");
export async function showDialog() {
utils.closeActiveDialog();
$addLinkTitleFormGroup.toggle(!hasSelection());
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
$autoComplete.val('').trigger('focus');
$linkTitle.val('');

View File

@@ -287,8 +287,6 @@ function initKoPlugins() {
}
export async function showDialog() {
utils.closeActiveDialog();
await libraryLoader.requireLibrary(libraryLoader.KNOCKOUT);
// lazily apply bindings on first use
@@ -300,11 +298,9 @@ export async function showDialog() {
ko.applyBindings(attributesModel, $dialog[0]);
}
glob.activeDialog = $dialog;
await attributesModel.loadAttributes();
$dialog.modal();
utils.openDialog($dialog);
}
$dialog.on('focus', '.attribute-name', function (e) {

View File

@@ -6,11 +6,7 @@ const $backendLogTextArea = $("#backend-log-textarea");
const $refreshBackendLog = $("#refresh-backend-log-button");
export async function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
load();
}

View File

@@ -13,10 +13,6 @@ const $noteTitle = $('#branch-prefix-note-title');
let branchId;
export async function showDialog(node) {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
branchId = node.data.branchId;
const branch = treeCache.getBranch(branchId);
@@ -30,7 +26,7 @@ export async function showDialog(node) {
return;
}
$dialog.modal();
utils.openDialog($dialog);
$treePrefixInput.val(branch.prefix);

View File

@@ -22,11 +22,7 @@ export async function showDialog(noteIds) {
}
}
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
$noteAutoComplete.val('').trigger('focus');

View File

@@ -17,8 +17,6 @@ let taskId = '';
let branchId = null;
export async function showDialog(node, defaultType) {
utils.closeActiveDialog();
// each opening of the dialog resets the taskId so we don't associate it with previous exports anymore
taskId = '';
$exportButton.removeAttr("disabled");
@@ -38,9 +36,7 @@ export async function showDialog(node, defaultType) {
$("#opml-v2").prop("checked", true); // setting default
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
branchId = node.data.branchId;

View File

@@ -3,9 +3,5 @@ import utils from "../services/utils.js";
const $dialog = $("#help-dialog");
export async function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
}

View File

@@ -16,8 +16,6 @@ const $explodeArchivesCheckbox = $("#explode-archives-checkbox");
let parentNoteId = null;
export async function showDialog(node) {
utils.closeActiveDialog();
$fileUploadInput.val('').trigger('change'); // to trigger Import button disabling listener below
$safeImportCheckbox.prop("checked", true);
@@ -26,13 +24,11 @@ export async function showDialog(node) {
$codeImportedAsCodeCheckbox.prop("checked", true);
$explodeArchivesCheckbox.prop("checked", true);
glob.activeDialog = $dialog;
parentNoteId = node.data.noteId;
$noteTitle.text(await treeUtils.getNoteTitle(parentNoteId));
$dialog.modal();
utils.openDialog($dialog);
}
$form.on('submit', () => {

View File

@@ -10,13 +10,9 @@ let callback = null;
export async function showDialog(cb) {
callback = cb;
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$autoComplete.val('');
$dialog.modal();
utils.openDialog($dialog);
noteAutocompleteService.initNoteAutocomplete($autoComplete, { hideGoToSelectedNoteButton: true });
noteAutocompleteService.showRecentNotes($autoComplete);

View File

@@ -10,13 +10,9 @@ let $originallyFocused; // element focused before the dialog was opened so we ca
export function info(message) {
$originallyFocused = $(':focus');
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$infoContent.text(message);
$dialog.modal();
utils.openDialog($dialog);
return new Promise((res, rej) => { resolve = res; });
}

View File

@@ -8,13 +8,9 @@ const $autoComplete = $("#jump-to-note-autocomplete");
const $showInFullTextButton = $("#show-in-full-text-button");
export async function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$autoComplete.val('');
$dialog.modal();
utils.openDialog($dialog);
noteAutocompleteService.initNoteAutocomplete($autoComplete, { hideGoToSelectedNoteButton: true })
.on('autocomplete:selected', function(event, suggestion, dataset) {

View File

@@ -16,10 +16,6 @@ function getOptions() {
}
export async function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
// set default settings
$maxNotesInput.val(20);
@@ -27,7 +23,7 @@ export async function showDialog() {
$linkMapContainer.empty();
$dialog.modal();
utils.openDialog($dialog);
}
$dialog.on('shown.bs.modal', () => {

View File

@@ -37,9 +37,7 @@ export async function importMarkdownInline() {
convertMarkdownToHtml(text);
}
else {
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
}
}

View File

@@ -1,7 +1,5 @@
import noteAutocompleteService from "../services/note_autocomplete.js";
import utils from "../services/utils.js";
import cloningService from "../services/cloning.js";
import treeUtils from "../services/tree_utils.js";
import toastService from "../services/toast.js";
import treeCache from "../services/tree_cache.js";
import treeChangesService from "../services/branches.js";
@@ -18,11 +16,7 @@ let movedNodes;
export async function showDialog(nodes) {
movedNodes = nodes;
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
$noteAutoComplete.val('').trigger('focus');

View File

@@ -10,11 +10,7 @@ const $mime = $("#note-info-mime");
const $okButton = $("#note-info-ok-button");
export function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
const activeNote = noteDetailService.getActiveTabNote();

View File

@@ -29,11 +29,7 @@ export async function showCurrentNoteRevisions() {
}
export async function showNoteRevisionsDialog(noteId, noteRevisionId) {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
await loadNoteRevisions(noteId, noteRevisionId);
}

View File

@@ -5,11 +5,7 @@ const $dialog = $("#note-source-dialog");
const $noteSource = $("#note-source");
export function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
const noteText = noteDetailService.getActiveTabNote().content;

View File

@@ -6,13 +6,9 @@ import utils from "../services/utils.js";
const $dialog = $("#options-dialog");
export async function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
const options = await server.get('options');
$dialog.modal();
utils.openDialog($dialog);
(await Promise.all([
import('./options/advanced.js'),

View File

@@ -37,6 +37,20 @@ const TPL = `
</div>
</div>
<div>
<h4>Note erasure timeout</h4>
<p>Deleted notes are at first only marked as deleted and it is possible to recover them
from Recent Notes dialog. After period of time, deleted notes are "erased" which means
their content is not recoverable anymore. This setting allows you to configure the length
of the period between deleting and erasing the note.</p>
<div class="form-group">
<label for="erase-notes-after-time-in-seconds">Erase notes after X seconds</label>
<input class="form-control" id="erase-notes-after-time-in-seconds" type="number" min="0">
</div>
</div>
<div>
<h4>Protected session timeout</h4>
@@ -81,6 +95,20 @@ export default class ProtectedSessionOptions {
return false;
});
this.$eraseNotesAfterTimeInSeconds = $("#erase-notes-after-time-in-seconds");
this.$eraseNotesAfterTimeInSeconds.on('change', () => {
const eraseNotesAfterTimeInSeconds = this.$eraseNotesAfterTimeInSeconds.val();
server.put('options', { 'eraseNotesAfterTimeInSeconds': eraseNotesAfterTimeInSeconds }).then(() => {
optionsService.reloadOptions();
toastService.showMessage("Options change have been saved.");
});
return false;
});
this.$protectedSessionTimeout = $("#protected-session-timeout-in-seconds");
this.$protectedSessionTimeout.on('change', () => {
@@ -126,6 +154,7 @@ export default class ProtectedSessionOptions {
this.$spellCheckEnabled.prop("checked", options['spellCheckEnabled'] === 'true');
this.$spellCheckLanguageCode.val(options['spellCheckLanguageCode']);
this.$eraseNotesAfterTimeInSeconds.val(options['eraseNotesAfterTimeInSeconds']);
this.$protectedSessionTimeout.val(options['protectedSessionTimeout']);
this.$noteRevisionsTimeInterval.val(options['noteRevisionSnapshotTimeInterval']);

View File

@@ -12,10 +12,6 @@ let resolve;
let shownCb;
export function ask({ message, defaultValue, shown }) {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
shownCb = shown;
$question = $("<label>")
@@ -34,7 +30,7 @@ export function ask({ message, defaultValue, shown }) {
.append($question)
.append($answer));
$dialog.modal();
utils.openDialog($dialog);
return new Promise((res, rej) => { resolve = res; });
}

View File

@@ -1,11 +1,12 @@
import protectedSessionService from "../services/protected_session.js";
import utils from "../services/utils.js";
const $dialog = $("#protected-session-password-dialog");
const $passwordForm = $dialog.find(".protected-session-password-form");
const $passwordInput = $dialog.find(".protected-session-password");
export function show() {
$dialog.modal();
utils.openDialog($dialog);
$passwordInput.trigger('focus');
}

View File

@@ -8,11 +8,7 @@ const $dialog = $("#recent-changes-dialog");
const $content = $("#recent-changes-content");
export async function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
const result = await server.get('recent-changes');
@@ -28,31 +24,60 @@ export async function showDialog() {
const groupedByDate = groupByDate(result);
for (const [dateDay, dayChanges] of groupedByDate) {
const changesListEl = $('<ul>');
const $changesList = $('<ul>');
const dayEl = $('<div>').append($('<b>').html(utils.formatDate(dateDay))).append(changesListEl);
const dayEl = $('<div>').append($('<b>').html(utils.formatDate(dateDay))).append($changesList);
for (const change of dayChanges) {
const formattedTime = utils.formatTime(utils.parseDate(change.date));
let noteLink;
let $noteLink;
if (change.current_isDeleted) {
noteLink = change.current_title;
$noteLink = $("<span>").text(change.current_title);
if (change.canBeUndeleted) {
const $undeleteLink = $(`<a href="javascript:">`)
.text("undelete")
.on('click', async () => {
const confirmDialog = await import('../dialogs/confirm.js');
const text = 'Do you want to undelete this note and its sub-notes?';
if (await confirmDialog.confirm(text)) {
await server.put(`notes/${change.noteId}/undelete`);
$dialog.modal('hide');
await treeCache.reloadNotes([change.noteId]);
treeService.activateNote(change.noteId);
}
});
$noteLink
.append(' (')
.append($undeleteLink)
.append(')');
}
}
else {
const note = await treeCache.getNote(change.noteId);
const notePath = await treeService.getSomeNotePath(note);
noteLink = await linkService.createNoteLink(notePath, {
title: change.title,
showNotePath: true
});
if (notePath) {
$noteLink = await linkService.createNoteLink(notePath, {
title: change.title,
showNotePath: true
});
}
else {
$noteLink = $("<span>").text(note.title);
}
}
changesListEl.append($('<li>')
$changesList.append($('<li>')
.append(formattedTime + ' - ')
.append(noteLink));
.append($noteLink));
}
$content.append(dayEl);
@@ -85,5 +110,6 @@ function groupByDate(result) {
groupedByDate.get(dateDay).push(row);
}
return groupedByDate;
}

View File

@@ -14,13 +14,9 @@ let codeEditor;
$dialog.on("shown.bs.modal", e => initEditor());
export async function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
await showTableSchemas();
$dialog.modal();
utils.openDialog($dialog);
}
async function initEditor() {

View File

@@ -37,12 +37,12 @@ $detail.on("click", ".close-detail-button",() => {
});
async function showTree() {
const tree = await treeService.loadTree();
const treeData = await treeService.loadTreeData();
$tree.fancytree({
autoScroll: true,
extensions: ["dnd5", "clones"],
source: tree,
source: treeData,
scrollParent: $tree,
minExpandLevel: 2, // root can't be collapsed
click: (event, data) => {
@@ -87,6 +87,8 @@ async function showTree() {
});
}
});
treeService.setTree($.ui.fancytree.getTree("#tree"));
}
$detail.on("click", ".note-menu-button", async e => {

View File

@@ -265,6 +265,24 @@ ws.subscribeToMessages(async message => {
}
});
ws.subscribeToMessages(async message => {
if (message.taskType !== 'undelete-notes') {
return;
}
if (message.type === 'task-error') {
toastService.closePersistent(message.taskId);
toastService.showError(message.message);
} else if (message.type === 'task-progress-count') {
toastService.showPersistent(makeToast(message.taskId, "Undeleting notes in progress: " + message.progressCount));
} else if (message.type === 'task-succeeded') {
const toast = makeToast(message.taskId, "Undeleting notes finished successfully.");
toast.closeAfter = 5000;
toastService.showPersistent(toast);
}
});
export default {
moveBeforeNode,
moveAfterNode,

View File

@@ -1,4 +1,4 @@
import treeUtils from "./tree_utils.js";
import treeService from "./tree.js";
import treeChangesService from "./branches.js";
import cloningService from "./cloning.js";
import toastService from "./toast.js";
@@ -19,7 +19,7 @@ async function pasteAfter(afterNode) {
}
if (clipboardMode === 'cut') {
const nodes = clipboardNodeKeys.map(nodeKey => treeUtils.getNodeByKey(nodeKey));
const nodes = clipboardNodeKeys.map(nodeKey => treeService.getNodeByKey(nodeKey));
await treeChangesService.moveAfterNode(nodes, afterNode);
@@ -28,7 +28,7 @@ async function pasteAfter(afterNode) {
}
else if (clipboardMode === 'copy') {
for (const nodeKey of clipboardNodeKeys) {
const clipNode = treeUtils.getNodeByKey(nodeKey);
const clipNode = treeService.getNodeByKey(nodeKey);
await cloningService.cloneNoteAfter(clipNode.data.noteId, afterNode.data.branchId);
}
@@ -46,7 +46,7 @@ async function pasteInto(parentNode) {
}
if (clipboardMode === 'cut') {
const nodes = clipboardNodeKeys.map(nodeKey => treeUtils.getNodeByKey(nodeKey));
const nodes = clipboardNodeKeys.map(nodeKey => treeService.getNodeByKey(nodeKey));
await treeChangesService.moveToNode(nodes, parentNode);
@@ -57,7 +57,7 @@ async function pasteInto(parentNode) {
}
else if (clipboardMode === 'copy') {
for (const nodeKey of clipboardNodeKeys) {
const clipNode = treeUtils.getNodeByKey(nodeKey);
const clipNode = treeService.getNodeByKey(nodeKey);
await cloningService.cloneNoteTo(clipNode.data.noteId, parentNode.data.noteId);
}
@@ -92,7 +92,7 @@ function cut(nodes) {
}
function isClipboardEmpty() {
clipboardNodeKeys = clipboardNodeKeys.filter(key => !!treeUtils.getNodeByKey(key));
clipboardNodeKeys = clipboardNodeKeys.filter(key => !!treeService.getNodeByKey(key));
return clipboardNodeKeys.length === 0;
}

View File

@@ -241,7 +241,7 @@ function registerEntrypoints() {
d.showDialog(selectedOrActiveNodes);
}));
keyboardActionService.setGlobalActionHandler("CreateNoteIntoDayNote", async () => {
const todayNote = await dateNoteService.getTodayNote();
@@ -288,6 +288,8 @@ function registerEntrypoints() {
searchNotesService.searchInSubtree(node.data.noteId);
});
keyboardActionService.setGlobalActionHandler("CopyWithoutFormatting", utils.copySelectionToClipboard);
}
export default {

View File

@@ -10,6 +10,12 @@ function getNotePathFromUrl(url) {
}
async function createNoteLink(notePath, options = {}) {
if (!notePath || !notePath.trim()) {
console.error("Missing note path");
return $("<span>").text("[missing note]");
}
let noteTitle = options.title;
const showTooltip = options.showTooltip === undefined ? true : options.showTooltip;
const showNotePath = options.showNotePath === undefined ? false : options.showNotePath;

View File

@@ -2,6 +2,7 @@ import treeService from './tree.js';
import treeCache from "./tree_cache.js";
import server from './server.js';
import toastService from "./toast.js";
import utils from "./utils.js";
const $searchInput = $("input[name='search-text']");
const $resetSearchButton = $("#reset-search-button");
@@ -28,6 +29,8 @@ const helpText = `
</p>`;
function showSearch() {
utils.saveFocusedElement();
$searchBox.slideDown();
$searchBox.tooltip({
@@ -49,6 +52,8 @@ function hideSearch() {
$searchResults.hide();
$searchBox.slideUp();
utils.focusSavedElement();
}
function toggleSearch() {

View File

@@ -5,6 +5,7 @@ import splitService from "./split.js";
import optionService from "./options.js";
import server from "./server.js";
import noteDetailService from "./note_detail.js";
import utils from "./utils.js";
const $sidebar = $("#right-pane");
const $sidebarContainer = $('#sidebar-container');
@@ -15,6 +16,10 @@ const $hideSidebarButton = $("#hide-sidebar-button");
optionService.waitForOptions().then(options => toggleSidebar(options.is('rightPaneVisible')));
function toggleSidebar(show) {
if (utils.isMobile()) {
return;
}
$sidebar.toggle(show);
$showSidebarButton.toggle(!show);
$hideSidebarButton.toggle(show);

View File

@@ -303,7 +303,11 @@ class TabContext {
let type = this.note.type;
if (type === 'text' && !disableAutoBook && utils.isHtmlEmpty(this.note.content) && this.note.hasChildren()) {
if (type === 'text'
&& !disableAutoBook
&& utils.isHtmlEmpty(this.note.content)
&& this.note.hasChildren()
&& utils.isDesktop()) {
type = 'book';
}

View File

@@ -16,6 +16,8 @@ import TreeContextMenu from "./tree_context_menu.js";
import bundle from "./bundle.js";
import keyboardActionService from "./keyboard_actions.js";
let tree;
const $tree = $("#tree");
const $createTopLevelNoteButton = $("#create-top-level-note-button");
const $collapseTreeButton = $("#collapse-tree-button");
@@ -30,8 +32,6 @@ const frontendLoaded = new Promise(resolve => { setFrontendAsLoaded = resolve; }
* @return {FancytreeNode|null}
*/
function getFocusedNode() {
const tree = $tree.fancytree("getTree");
return tree.getFocusNode();
}
@@ -40,7 +40,7 @@ function getFocusedNode() {
* @return {FancytreeNode|null}
*/
function getActiveNode() {
return $tree.fancytree("getActiveNode");
return tree.getActiveNode();
}
/** @return {FancytreeNode[]} */
@@ -56,7 +56,7 @@ async function getNodesByBranchId(branchId) {
function getNodesByNoteId(noteId) {
utils.assertArguments(noteId);
const list = getTree().getNodesByRef(noteId);
const list = tree.getNodesByRef(noteId);
return list ? list : []; // if no nodes with this refKey are found, fancy tree returns null
}
@@ -309,7 +309,7 @@ async function getSomeNotePath(note) {
const parents = await cur.getParentNotes();
if (!parents.length) {
toastService.throwError(`Can't find parents for note ${cur.noteId}`);
console.error(`Can't find parents for note ${cur.noteId}`);
return;
}
@@ -331,7 +331,7 @@ async function setExpandedToServer(branchId, isExpanded) {
/** @return {FancytreeNode[]} */
function getSelectedNodes(stopOnParents = false) {
return getTree().getSelectedNodes(stopOnParents);
return tree.getSelectedNodes(stopOnParents);
}
/** @return {FancytreeNode[]} */
@@ -429,14 +429,14 @@ async function treeInitialized() {
setFrontendAsLoaded();
}
async function initFancyTree(tree) {
utils.assertArguments(tree);
async function initFancyTree(treeData) {
utils.assertArguments(treeData);
$tree.fancytree({
autoScroll: true,
keyboard: false, // we takover keyboard handling in the hotkeys plugin
extensions: ["hotkeys", "dnd5", "clones"],
source: tree,
source: treeData,
scrollParent: $tree,
minExpandLevel: 2, // root can't be collapsed
click: (event, data) => {
@@ -523,19 +523,16 @@ async function initFancyTree(tree) {
return false; // blocks default browser right click menu
});
}
/** @return {Fancytree} */
function getTree() {
return $tree.fancytree('getTree');
tree = $.ui.fancytree.getTree("#tree");
}
async function reload() {
const notes = await loadTree();
const notes = await loadTreeData();
const activeNotePath = getActiveNode() !== null ? await treeUtils.getNotePath(getActiveNode()) : null;
await getTree().reload(notes);
await tree.reload(notes);
// reactivate originally activated node, but don't trigger note loading
if (activeNotePath) {
@@ -559,7 +556,7 @@ function getHashValueFromAddress() {
return str.split("-");
}
async function loadTree() {
async function loadTreeData() {
const resp = await server.get('tree');
treeCache.load(resp.notes, resp.branches);
@@ -580,7 +577,7 @@ async function collapseTree(node = null) {
}
function focusTree() {
getTree().setFocus();
tree.setFocus();
}
async function scrollToActiveNote() {
@@ -638,7 +635,7 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) {
extraOptions.saveSelection = false;
}
if (extraOptions.saveSelection) {
if (extraOptions.saveSelection && utils.isCKEditorInitialized()) {
[extraOptions.title, extraOptions.content] = parseSelectedHtml(window.cutToNote.getSelectedHtml());
}
@@ -651,7 +648,7 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) {
type: extraOptions.type
});
if (extraOptions.saveSelection) {
if (extraOptions.saveSelection && utils.isCKEditorInitialized()) {
// we remove the selection only after it was saved to server to make sure we don't lose anything
window.cutToNote.removeSelection();
}
@@ -754,9 +751,9 @@ async function sortAlphabetically(noteId) {
}
async function showTree() {
const tree = await loadTree();
const treeData = await loadTreeData();
await initFancyTree(tree);
await initFancyTree(treeData);
}
ws.subscribeToMessages(message => {
@@ -873,7 +870,7 @@ window.glob.cutIntoNote = () => createNoteInto(true);
keyboardActionService.setGlobalActionHandler('CutIntoNote', () => createNoteInto(true));
keyboardActionService.setGlobalActionHandler('CreateNoteInto', createNoteInto);
keyboardActionService.setGlobalActionHandler('CreateNoteInto', () => createNoteInto(true));
keyboardActionService.setGlobalActionHandler('ScrollToActiveNote', scrollToActiveNote);
@@ -912,6 +909,13 @@ async function duplicateNote(noteId, parentNoteId) {
toastService.showMessage(`Note "${origNote.title}" has been duplicated`);
}
function getNodeByKey(key) {
return tree.getNodeByKey(key);
}
function setTree(treeInstance) {
tree = treeInstance;
}
keyboardActionService.setGlobalActionHandler('CollapseTree', () => collapseTree()); // don't use shortened form since collapseTree() accepts argument
$collapseTreeButton.on('click', () => collapseTree());
@@ -931,13 +935,12 @@ export default {
setNoteTitle,
setPrefix,
createNote,
createNoteInto,
getSelectedNodes,
getSelectedOrActiveNodes,
clearSelectedNodes,
sortAlphabetically,
showTree,
loadTree,
loadTreeData,
treeInitialized,
setExpandedToServer,
getNodesByNoteId,
@@ -949,5 +952,7 @@ export default {
getSomeNotePath,
focusTree,
scrollToActiveNote,
duplicateNote
duplicateNote,
getNodeByKey,
setTree
};

View File

@@ -62,6 +62,11 @@ class TreeContextMenu {
!isHoisted || !isNotRoot ? null : { title: 'Unhoist note <kbd data-kb-action="ToggleNoteHoisting"></kbd>', cmd: "unhoist", uiIcon: "arrow-up" },
{ title: 'Edit branch prefix <kbd data-kb-action="EditBranchPrefix"></kbd>', cmd: "editBranchPrefix", uiIcon: "empty",
enabled: isNotRoot && parentNotSearch && noSelectedNotes},
{ title: "Advanced", uiIcon: "empty", enabled: true, items: [
{ title: 'Collapse subtree <kbd data-kb-action="CollapseSubtree"></kbd>', cmd: "collapseSubtree", uiIcon: "align-justify", enabled: noSelectedNotes },
{ title: "Force note sync", cmd: "forceNoteSync", uiIcon: "recycle", enabled: noSelectedNotes },
{ title: 'Sort alphabetically <kbd data-kb-action="SortChildNotes"></kbd>', cmd: "sortAlphabetically", uiIcon: "empty", enabled: noSelectedNotes && notSearch }
] },
{ title: "----" },
{ title: "Protect subtree", cmd: "protectSubtree", uiIcon: "check-shield", enabled: noSelectedNotes },
{ title: "Unprotect subtree", cmd: "unprotectSubtree", uiIcon: "shield", enabled: noSelectedNotes },
@@ -84,12 +89,7 @@ class TreeContextMenu {
{ title: "Export", cmd: "export", uiIcon: "empty",
enabled: notSearch && noSelectedNotes },
{ title: "Import into note", cmd: "importIntoNote", uiIcon: "empty",
enabled: notSearch && noSelectedNotes },
{ title: "Advanced", uiIcon: "empty", enabled: true, items: [
{ title: 'Collapse subtree <kbd data-kb-action="CollapseSubtree"></kbd>', cmd: "collapseSubtree", uiIcon: "align-justify", enabled: noSelectedNotes },
{ title: "Force note sync", cmd: "forceNoteSync", uiIcon: "recycle", enabled: noSelectedNotes },
{ title: 'Sort alphabetically <kbd data-kb-action="SortChildNotes"></kbd>', cmd: "sortAlphabetically", uiIcon: "empty", enabled: noSelectedNotes && notSearch }
] },
enabled: notSearch && noSelectedNotes }
].filter(row => row !== null);
}

View File

@@ -2,16 +2,10 @@ import utils from './utils.js';
import hoistedNoteService from './hoisted_note.js';
import treeCache from "./tree_cache.js";
const $tree = $("#tree");
async function getParentProtectedStatus(node) {
return await hoistedNoteService.isRootNode(node) ? 0 : node.getParent().data.isProtected;
}
function getNodeByKey(key) {
return $tree.fancytree('getNodeByKey', key);
}
function getNoteIdFromNotePath(notePath) {
if (!notePath) {
return null;
@@ -123,7 +117,6 @@ async function getNotePathTitle(notePath) {
export default {
getParentProtectedStatus,
getNodeByKey,
getNotePath,
getNoteIdFromNotePath,
getNoteIdAndParentIdFromNotePath,

View File

@@ -209,9 +209,50 @@ function getMimeTypeClass(mime) {
function closeActiveDialog() {
if (glob.activeDialog) {
glob.activeDialog.modal('hide');
glob.activeDialog = null;
}
}
let $lastFocusedElement = null;
function saveFocusedElement() {
$lastFocusedElement = $(":focus");
}
function focusSavedElement() {
if (!$lastFocusedElement) {
return;
}
if ($lastFocusedElement.hasClass("ck")) {
// must handle CKEditor separately because of this bug: https://github.com/ckeditor/ckeditor5/issues/607
import("./note_detail.js").then(noteDetail => {
noteDetail.default.getActiveEditor().editing.view.focus();
});
} else {
$lastFocusedElement.focus();
}
$lastFocusedElement = null;
}
function openDialog($dialog) {
closeActiveDialog();
glob.activeDialog = $dialog;
saveFocusedElement();
$dialog.modal();
$dialog.on('hidden.bs.modal', () => {
if (!glob.activeDialog || glob.activeDialog === $dialog) {
focusSavedElement();
}
});
}
function isHtmlEmpty(html) {
html = html.toLowerCase();
@@ -241,6 +282,17 @@ function getUrlForDownload(url) {
}
}
function copySelectionToClipboard() {
const text = window.getSelection().toString();
if (navigator.clipboard) {
navigator.clipboard.writeText(text);
}
}
function isCKEditorInitialized() {
return !!(window && window.cutToNote);
}
export default {
reloadApp,
parseDate,
@@ -270,8 +322,13 @@ export default {
getNoteTypeClass,
getMimeTypeClass,
closeActiveDialog,
openDialog,
saveFocusedElement,
focusSavedElement,
isHtmlEmpty,
clearBrowserCache,
getUrlForDownload,
normalizeShortcut
normalizeShortcut,
copySelectionToClipboard,
isCKEditorInitialized
};

View File

@@ -71,7 +71,19 @@ class AttributesWidget extends StandardWidget {
async renderAttributes(attributes, $container) {
for (const attribute of attributes) {
if (attribute.type === 'label') {
$container.append(utils.formatLabel(attribute) + " ");
if (attribute.name === 'externalLink') {
$container.append('@' + attribute.name + "=");
$container.append(
$('<a>')
.text(attribute.value)
.attr('href', attribute.value)
.addClass('external')
);
$container.append(" ");
}
else {
$container.append(utils.formatLabel(attribute) + " ");
}
} else if (attribute.type === 'relation') {
if (attribute.value) {
$container.append('@' + attribute.name + "=");

View File

@@ -41,7 +41,7 @@ class CalendarWidget extends StandardWidget {
}
init($el, activeDate) {
this.activeDate = new Date(Date.parse(activeDate));
this.activeDate = new Date(activeDate + "T12:00:00"); // attaching time fixes local timezone handling
this.todaysDate = new Date();
this.date = new Date(this.activeDate.getTime());

View File

@@ -101,7 +101,8 @@ async function deleteBranch(req) {
const branch = await repository.getBranch(req.params.branchId);
const taskContext = TaskContext.getInstance(req.query.taskId, 'delete-notes');
const noteDeleted = await notes.deleteBranch(branch, taskContext);
const deleteId = utils.randomString(10);
const noteDeleted = await notes.deleteBranch(branch, deleteId, taskContext);
if (last) {
taskContext.taskSucceeded();

View File

@@ -1,5 +1,6 @@
"use strict";
const attributeService = require("../../services/attributes");
const noteService = require('../../services/notes');
const dateNoteService = require('../../services/date_notes');
const dateUtils = require('../../services/date_utils');
@@ -23,16 +24,26 @@ async function findClippingNote(todayNote, pageUrl) {
return null;
}
async function getClipperInboxNote() {
let clipperInbox = await attributeService.getNoteWithLabel('clipperInbox');
if (!clipperInbox) {
clipperInbox = await dateNoteService.getDateNote(dateUtils.localNowDate());
}
return clipperInbox;
}
async function addClipping(req) {
const {title, content, pageUrl, images} = req.body;
const todayNote = await dateNoteService.getDateNote(dateUtils.localNowDate());
const clipperInbox = await getClipperInboxNote();
let clippingNote = await findClippingNote(todayNote, pageUrl);
let clippingNote = await findClippingNote(clipperInbox, pageUrl);
if (!clippingNote) {
clippingNote = (await noteService.createNewNote({
parentNoteId: todayNote.noteId,
parentNoteId: clipperInbox.noteId,
title: title,
content: '',
type: 'text'
@@ -54,10 +65,10 @@ async function addClipping(req) {
async function createNote(req) {
const {title, content, pageUrl, images, clipType} = req.body;
const todayNote = await dateNoteService.getDateNote(dateUtils.localNowDate());
const clipperInbox = await getClipperInboxNote();
const {note} = await noteService.createNewNote({
parentNoteId: todayNote.noteId,
parentNoteId: clipperInbox.noteId,
title,
content,
type: 'text'

View File

@@ -55,12 +55,15 @@ async function deleteNote(req) {
const taskId = req.query.taskId;
const last = req.query.last === 'true';
// note how deleteId is separate from taskId - single taskId produces separate deleteId for each "top level" deleted note
const deleteId = utils.randomString(10);
const note = await repository.getNote(noteId);
const taskContext = TaskContext.getInstance(taskId, 'delete-notes');
for (const branch of await note.getBranches()) {
await noteService.deleteBranch(branch, taskContext);
await noteService.deleteBranch(branch, deleteId, taskContext);
}
if (last) {
@@ -68,6 +71,16 @@ async function deleteNote(req) {
}
}
async function undeleteNote(req) {
const note = await repository.getNote(req.params.noteId);
const taskContext = TaskContext.getInstance(utils.randomString(10), 'undelete-notes');
await noteService.undeleteNote(note, note.deleteId, taskContext);
await taskContext.taskSucceeded();
}
async function sortNotes(req) {
const noteId = req.params.noteId;
@@ -169,6 +182,7 @@ module.exports = {
getNote,
updateNote,
deleteNote,
undeleteNote,
createNote,
sortNotes,
protectSubtree,

View File

@@ -6,6 +6,7 @@ const attributes = require('../../services/attributes');
// options allowed to be updated directly in options dialog
const ALLOWED_OPTIONS = new Set([
'eraseNotesAfterTimeInSeconds',
'protectedSessionTimeout',
'noteRevisionSnapshotTimeInterval',
'zoomFactor',

View File

@@ -2,6 +2,7 @@
const sql = require('../../services/sql');
const protectedSessionService = require('../../services/protected_session');
const noteService = require('../../services/notes');
async function getRecentChanges() {
const recentChanges = await sql.getRows(
@@ -10,6 +11,8 @@ async function getRecentChanges() {
SELECT
notes.noteId,
notes.isDeleted AS current_isDeleted,
notes.deleteId AS current_deleteId,
notes.isErased AS current_isErased,
notes.title AS current_title,
notes.isProtected AS current_isProtected,
note_revisions.title,
@@ -19,21 +22,23 @@ async function getRecentChanges() {
JOIN notes USING(noteId)
ORDER BY
note_revisions.utcDateCreated DESC
LIMIT 1000
LIMIT 200
)
UNION ALL SELECT * FROM (
SELECT
notes.noteId,
notes.isDeleted AS current_isDeleted,
notes.deleteId AS current_deleteId,
notes.isErased AS current_isErased,
notes.title AS current_title,
notes.isProtected AS current_isProtected,
notes.title,
notes.utcDateCreated AS date
notes.utcDateModified AS date
FROM
notes
ORDER BY
utcDateCreated DESC
LIMIT 1000
utcDateModified DESC
LIMIT 200
)
ORDER BY date DESC
LIMIT 200`);
@@ -48,6 +53,20 @@ async function getRecentChanges() {
change.title = change.current_title = "[Protected]";
}
}
if (change.current_isDeleted) {
if (change.current_isErased) {
change.canBeUndeleted = false;
}
else {
const deleteId = change.current_deleteId;
const undeletedParentBranches = await noteService.getUndeletedParentBranches(change.noteId, deleteId);
// note (and the subtree) can be undeleted if there's at least one undeleted parent (whose branch would be undeleted by this op)
change.canBeUndeleted = undeletedParentBranches.length > 0;
}
}
}
return recentChanges;

View File

@@ -29,8 +29,12 @@ async function searchFromNote(req) {
return [404, `Note ${req.params.noteId} has not been found.`];
}
if (note.isDeleted) {
return [400, `Note ${req.params.noteId} is deleted.`];
}
if (note.type !== 'search') {
return [400, '`Note ${req.params.noteId} is not search note.`']
return [400, `Note ${req.params.noteId} is not search note.`]
}
const json = await note.getJsonContent();
@@ -41,18 +45,28 @@ async function searchFromNote(req) {
let noteIds;
if (json.searchString.startsWith('=')) {
const relationName = json.searchString.substr(1).trim();
try {
if (json.searchString.startsWith('=')) {
const relationName = json.searchString.substr(1).trim();
noteIds = await searchFromRelation(note, relationName);
noteIds = await searchFromRelation(note, relationName);
} else {
noteIds = await searchService.searchForNoteIds(json.searchString);
}
}
else {
noteIds = await searchService.searchForNoteIds(json.searchString);
catch (e) {
log.error(`Search failed for note ${note.noteId}: ` + e.message + ": " + e.stack);
throw new Error("Search failed, see logs for details.");
}
// we won't return search note's own noteId
noteIds = noteIds.filter(noteId => noteId !== note.noteId);
if (noteIds.length > 200) {
noteIds = noteIds.slice(0, 200);
}
return noteIds.map(noteCacheService.getNotePath).filter(res => !!res);
}

View File

@@ -131,6 +131,7 @@ function register(app) {
apiRoute(GET, '/api/notes/:noteId', notesApiRoute.getNote);
apiRoute(PUT, '/api/notes/:noteId', notesApiRoute.updateNote);
apiRoute(DELETE, '/api/notes/:noteId', notesApiRoute.deleteNote);
apiRoute(PUT, '/api/notes/:noteId/undelete', notesApiRoute.undeleteNote);
apiRoute(POST, '/api/notes/:parentNoteId/children', notesApiRoute.createNote);
apiRoute(PUT, '/api/notes/:noteId/sort', notesApiRoute.sortNotes);
apiRoute(PUT, '/api/notes/:noteId/protect/:isProtected', notesApiRoute.protectSubtree);

View File

@@ -4,8 +4,8 @@ const build = require('./build');
const packageJson = require('../../package');
const {TRILIUM_DATA_DIR} = require('./data_dir');
const APP_DB_VERSION = 155;
const SYNC_VERSION = 13;
const APP_DB_VERSION = 157;
const SYNC_VERSION = 14;
const CLIPPER_PROTOCOL_VERSION = "1.0";
module.exports = {

View File

@@ -1 +1 @@
module.exports = { buildDate:"2020-01-18T20:52:14+01:00", buildRevision: "0ec11d29ba2d524ff80b49ab8dc40f3f8cf3835b" };
module.exports = { buildDate:"2020-02-09T10:48:23+01:00", buildRevision: "88bd65c6798609a39206722305fab4f8d91d618b" };

View File

@@ -7,6 +7,7 @@ const noteService = require('./notes');
const repository = require('./repository');
const Branch = require('../entities/branch');
const TaskContext = require("./task_context.js");
const utils = require('./utils');
async function cloneNoteToParent(noteId, parentNoteId, prefix) {
if (await isNoteDeleted(noteId) || await isNoteDeleted(parentNoteId)) {
@@ -54,7 +55,8 @@ async function ensureNoteIsAbsentFromParent(noteId, parentNoteId) {
const branch = await repository.getEntity(`SELECT * FROM branches WHERE noteId = ? AND parentNoteId = ? AND isDeleted = 0`, [noteId, parentNoteId]);
if (branch) {
await noteService.deleteBranch(branch, new TaskContext());
const deleteId = utils.randomString(10);
await noteService.deleteBranch(branch, deleteId, new TaskContext());
}
}

View File

@@ -1,5 +1,5 @@
const sax = require("sax");
const fileType = require('file-type');
const FileType = require('file-type');
const stream = require('stream');
const log = require("../log");
const utils = require("../utils");
@@ -242,7 +242,7 @@ async function importEnex(taskContext, file, parentNote) {
const mediaRegex = new RegExp(`<en-media hash="${hash}"[^>]*>`, 'g');
const fileTypeFromBuffer = fileType(resource.content);
const fileTypeFromBuffer = await FileType.fromBuffer(resource.content);
if (fileTypeFromBuffer) {
// If fileType returns something for buffer, then set the mime given
resource.mime = fileTypeFromBuffer.mime;

View File

@@ -152,6 +152,11 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
continue;
}
if (attr.type === 'label' && attr.name === 'externalLink') {
// also created automatically
continue;
}
if (attr.type === 'relation') {
attr.value = getNewNoteId(attr.value);
}

View File

@@ -306,6 +306,10 @@ const DEFAULT_KEYBOARD_ACTIONS = [
{
actionName: "ZoomIn",
defaultShortcuts: ["CommandOrControl+="]
},
{
actionName: "CopyWithoutFormatting",
defaultShortcuts: ["CommandOrControl+Alt+C"]
}
];

View File

@@ -242,6 +242,20 @@ function findInternalLinks(content, foundLinks) {
return content.replace(/href="[^"]*#root/g, 'href="#root');
}
function findExternalLinks(content, foundLinks) {
const re = /href="([a-zA-Z]+:\/\/[^"]*)"/g;
let match;
while (match = re.exec(content)) {
foundLinks.push({
name: 'externalLink',
value: match[1]
});
}
return content;
}
function findIncludeNoteLinks(content, foundLinks) {
const re = /<section class="include-note" data-note-id="([a-zA-Z0-9]+)">/g;
let match;
@@ -281,6 +295,7 @@ async function saveLinks(note, content) {
if (note.type === 'text') {
content = findImageLinks(content, foundLinks);
content = findInternalLinks(content, foundLinks);
content = findExternalLinks(content, foundLinks);
content = findIncludeNoteLinks(content, foundLinks);
}
else if (note.type === 'relation-map') {
@@ -293,9 +308,11 @@ async function saveLinks(note, content) {
const existingLinks = await note.getLinks();
for (const foundLink of foundLinks) {
const targetNote = await repository.getNote(foundLink.value);
if (!targetNote || targetNote.isDeleted) {
continue;
if (foundLink.name !== 'externalLink') {
const targetNote = await repository.getNote(foundLink.value);
if (!targetNote || targetNote.isDeleted) {
continue;
}
}
const existingLink = existingLinks.find(existingLink =>
@@ -305,7 +322,7 @@ async function saveLinks(note, content) {
if (!existingLink) {
await new Attribute({
noteId: note.noteId,
type: 'relation',
type: foundLink.name === 'externalLink' ? 'label' : 'relation',
name: foundLink.name,
value: foundLink.value,
}).save();
@@ -404,8 +421,14 @@ async function updateNote(noteId, noteUpdates) {
};
}
/** @return {boolean} - true if note has been deleted, false otherwise */
async function deleteBranch(branch, taskContext) {
/**
* @param {Branch} branch
* @param {string} deleteId
* @param {TaskContext} taskContext
*
* @return {boolean} - true if note has been deleted, false otherwise
*/
async function deleteBranch(branch, deleteId, taskContext) {
taskContext.increaseProgressCount();
if (!branch || branch.isDeleted) {
@@ -420,26 +443,32 @@ async function deleteBranch(branch, taskContext) {
}
branch.isDeleted = true;
branch.deleteId = deleteId;
await branch.save();
const note = await branch.getNote();
const notDeletedBranches = await note.getBranches();
if (notDeletedBranches.length === 0) {
note.isDeleted = true;
await note.save();
for (const childBranch of await note.getChildBranches()) {
await deleteBranch(childBranch, taskContext);
await deleteBranch(childBranch, deleteId, taskContext);
}
// first delete children and then parent - this will show up better in recent changes
note.isDeleted = true;
note.deleteId = deleteId;
await note.save();
for (const attribute of await note.getOwnedAttributes()) {
attribute.isDeleted = true;
attribute.deleteId = deleteId;
await attribute.save();
}
for (const relation of await note.getTargetRelations()) {
relation.isDeleted = true;
relation.deleteId = deleteId;
await relation.save();
}
@@ -450,6 +479,88 @@ async function deleteBranch(branch, taskContext) {
}
}
/**
* @param {Note} note
* @param {string} deleteId
* @param {TaskContext} taskContext
*/
async function undeleteNote(note, deleteId, taskContext) {
const undeletedParentBranches = await getUndeletedParentBranches(note.noteId, deleteId);
if (undeletedParentBranches.length === 0) {
// cannot undelete if there's no undeleted parent
return;
}
for (const parentBranch of undeletedParentBranches) {
await undeleteBranch(parentBranch, deleteId, taskContext);
}
}
/**
* @param {Branch} branch
* @param {string} deleteId
* @param {TaskContext} taskContext
*/
async function undeleteBranch(branch, deleteId, taskContext) {
if (!branch.isDeleted) {
return;
}
const note = await branch.getNote();
if (note.isDeleted && note.deleteId !== deleteId) {
return;
}
branch.isDeleted = false;
await branch.save();
taskContext.increaseProgressCount();
if (note.isDeleted && note.deleteId === deleteId) {
note.isDeleted = false;
await note.save();
const attrs = await repository.getEntities(`
SELECT * FROM attributes
WHERE isDeleted = 1
AND deleteId = ?
AND (noteId = ?
OR (type = 'relation' AND value = ?))`, [deleteId, note.noteId, note.noteId]);
for (const attr of attrs) {
attr.isDeleted = false;
await attr.save();
}
const childBranches = await repository.getEntities(`
SELECT branches.*
FROM branches
WHERE branches.isDeleted = 1
AND branches.deleteId = ?
AND branches.parentNoteId = ?`, [deleteId, note.noteId]);
for (const childBranch of childBranches) {
await undeleteBranch(childBranch, deleteId, taskContext);
}
}
}
/**
* @return return deleted branches of an undeleted parent note
*/
async function getUndeletedParentBranches(noteId, deleteId) {
return await repository.getEntities(`
SELECT branches.*
FROM branches
JOIN notes AS parentNote ON parentNote.noteId = branches.parentNoteId
WHERE branches.noteId = ?
AND branches.isDeleted = 1
AND branches.deleteId = ?
AND parentNote.isDeleted = 0`, [noteId, deleteId]);
}
async function scanForLinks(noteId) {
const note = await repository.getNote(noteId);
if (!note || !['text', 'relation-map'].includes(note.type)) {
@@ -468,7 +579,9 @@ async function scanForLinks(noteId) {
}
async function eraseDeletedNotes() {
const cutoffDate = new Date(Date.now() - 48 * 3600 * 1000);
const eraseNotesAfterTimeInSeconds = await optionService.getOptionInt('eraseNotesAfterTimeInSeconds');
const cutoffDate = new Date(Date.now() - eraseNotesAfterTimeInSeconds * 1000);
const noteIdsToErase = await sql.getColumn("SELECT noteId FROM notes WHERE isDeleted = 1 AND isErased = 0 AND notes.utcDateModified <= ?", [dateUtils.utcDateStr(cutoffDate)]);
@@ -482,10 +595,11 @@ async function eraseDeletedNotes() {
// - we don't want change the hash since this erasing happens on each instance separately
// and changing the hash would fire up the sync errors temporarily
// setting contentLength to zero would serve no benefit and it leaves potentially useful trail
await sql.executeMany(`
UPDATE notes
SET isErased = 1
SET title = '[deleted]',
contentLength = 0,
isErased = 1
WHERE noteId IN (???)`, noteIdsToErase);
await sql.executeMany(`
@@ -503,9 +617,16 @@ async function eraseDeletedNotes() {
await sql.executeMany(`
UPDATE note_revisions
SET isErased = 1,
title = NULL
title = NULL,
contentLength = 0
WHERE isErased = 0 AND noteId IN (???)`, noteIdsToErase);
await sql.executeMany(`
UPDATE attributes
SET name = 'deleted',
value = ''
WHERE noteId IN (???)`, noteIdsToErase);
log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
}
@@ -559,7 +680,9 @@ module.exports = {
createNewNoteWithTarget,
updateNote,
deleteBranch,
undeleteNote,
protectNoteRecursively,
scanForLinks,
duplicateNote
duplicateNote,
getUndeletedParentBranches
};

View File

@@ -80,7 +80,8 @@ const defaultOptions = [
{ name: 'leftPaneWidth', value: '25', isSynced: false },
{ name: 'rightPaneWidth', value: '25', isSynced: false },
{ name: 'rightPaneVisible', value: 'true', isSynced: false },
{ name: 'nativeTitleBarVisible', value: 'false', isSynced: false }
{ name: 'nativeTitleBarVisible', value: 'false', isSynced: false },
{ name: 'eraseNotesAfterTimeInSeconds', value: '604800', isSynced: true } // default is 7 days
];
async function initStartupOptions() {

View File

@@ -2,7 +2,6 @@ const sql = require('./sql');
const ScriptContext = require('./script_context');
const repository = require('./repository');
const cls = require('./cls');
const sourceIdService = require('./source_id');
const log = require('./log');
async function executeNote(note, apiParams) {