Compare commits
111 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a890b91079 | |||
| e0aabe4f9c | |||
| 01cd9d8fb3 | |||
| af10f0f52a | |||
| aa5ede5039 | |||
| 21e77c83fc | |||
| b0310e34e2 | |||
| 9812b9c272 | |||
| 92d5f91aa6 | |||
| b0368c7f17 | |||
| 8e1f8c869b | |||
| 4688cda493 | |||
| 761c51069a | |||
| 4dc285d84f | |||
| 0e2f8b5734 | |||
| a1402c7c66 | |||
| 6ba3e5ab7f | |||
| f740e52ebf | |||
| e9454e4db7 | |||
| 749bb90713 | |||
| eb8c296e62 | |||
| dc063983ea | |||
| 2595c3ac31 | |||
| 9cb8bc5dd8 | |||
| 3b690f5456 | |||
| 7ef2e7769f | |||
| 4c07ac4c4c | |||
| 35cd7f3261 | |||
| 8c3e2e5eb7 | |||
| d57caee0d3 | |||
| 6e83980784 | |||
| 295af1f43e | |||
| ed2afe5c20 | |||
| bfc7570e14 | |||
| d9b9d730bb | |||
| ba8a8dca7b | |||
| 29eb5a8435 | |||
| 5de92171a7 | |||
| 29c5e394ab | |||
| 07b3d11fe5 | |||
| 67663fba50 | |||
| 995ebbf577 | |||
| d0e6be3e0c | |||
| 01370a5968 | |||
| 6c561b5764 | |||
| 2953f1bdb8 | |||
| 1c057cac75 | |||
| 0415efd33b | |||
| e58dc829f5 | |||
| 90d10c1ff3 | |||
| 5b30291601 | |||
| 5193f073e9 | |||
| 6c7d8a9667 | |||
| 5e9bedd903 | |||
| e712990c03 | |||
| 91487b338a | |||
| 3ff24d53e5 | |||
| 94c904fb40 | |||
| 5f258fbbbf | |||
| 56c7b7f5bd | |||
| bf9ad976b9 | |||
| 19206d1e0d | |||
| 420be6d8c6 | |||
| dbd2040bee | |||
| 1e979d71c7 | |||
| 6bbd4c59bc | |||
| 3a54d00e2b | |||
| 499c9a7381 | |||
| cd139bdd76 | |||
| 60c3b5cccc | |||
| 540f9f933a | |||
| a59943094e | |||
| c400a7143c | |||
| 1f37d00e42 | |||
| d21e824343 | |||
| ff3f0ee0a0 | |||
| 01ff34b5d4 | |||
| 0cde7ede24 | |||
| 92cb723d0c | |||
| e4bec265c1 | |||
| 434d8ef48c | |||
| c8ba07a4ae | |||
| 4da6234911 | |||
| ff0245f05f | |||
| 98c81faedb | |||
| 465c3b87a7 | |||
| 0e5028acd3 | |||
| 00c295e4bf | |||
| 7084ed4fb1 | |||
| 587134c2f8 | |||
| 5fac2c7633 | |||
| 08a518479b | |||
| bcdfb47939 | |||
| 667471e7bb | |||
| 3c4ec7fe1a | |||
| 4bd7438fca | |||
| 0ae9c8da17 | |||
| f921562346 | |||
| 643d9077fc | |||
| b4709e8ee5 | |||
| a1181623b7 | |||
| 73a6c66379 | |||
| 1d5daa8dfd | |||
| c141f4b2c0 | |||
| 767aaa18f4 | |||
| 8a7228146c | |||
| 3d294c5163 | |||
| 60231de0ed | |||
| 13c0411533 | |||
| de02e9e889 | |||
| e143becb7a |
+109
-112
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<dataSource name="document.db">
|
||||
<database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.16">
|
||||
<database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.17">
|
||||
<root id="1">
|
||||
<ServerVersion>3.25.1</ServerVersion>
|
||||
</root>
|
||||
@@ -134,509 +134,506 @@
|
||||
<ColNames>name
|
||||
value</ColNames>
|
||||
</index>
|
||||
<index id="40" parent="7" name="IDX_attributes_name_index">
|
||||
<ColNames>name</ColNames>
|
||||
</index>
|
||||
<index id="41" parent="7" name="IDX_attributes_value_index">
|
||||
<index id="40" parent="7" name="IDX_attributes_value_index">
|
||||
<ColNames>value</ColNames>
|
||||
</index>
|
||||
<key id="42" parent="7">
|
||||
<key id="41" parent="7">
|
||||
<ColNames>attributeId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_attributes_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="43" parent="8" name="branchId">
|
||||
<column id="42" parent="8" name="branchId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="44" parent="8" name="noteId">
|
||||
<column id="43" parent="8" name="noteId">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="45" parent="8" name="parentNoteId">
|
||||
<column id="44" parent="8" name="parentNoteId">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="46" parent="8" name="notePosition">
|
||||
<column id="45" parent="8" name="notePosition">
|
||||
<Position>4</Position>
|
||||
<DataType>INTEGER|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="47" parent="8" name="prefix">
|
||||
<column id="46" parent="8" name="prefix">
|
||||
<Position>5</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
</column>
|
||||
<column id="48" parent="8" name="isExpanded">
|
||||
<column id="47" parent="8" name="isExpanded">
|
||||
<Position>6</Position>
|
||||
<DataType>INTEGER|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="49" parent="8" name="isDeleted">
|
||||
<column id="48" parent="8" name="isDeleted">
|
||||
<Position>7</Position>
|
||||
<DataType>INTEGER|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="50" parent="8" name="utcDateModified">
|
||||
<column id="49" parent="8" name="utcDateModified">
|
||||
<Position>8</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="51" parent="8" name="utcDateCreated">
|
||||
<column id="50" parent="8" name="utcDateCreated">
|
||||
<Position>9</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="52" parent="8" name="hash">
|
||||
<column id="51" parent="8" name="hash">
|
||||
<Position>10</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>""</DefaultExpression>
|
||||
</column>
|
||||
<index id="53" parent="8" name="sqlite_autoindex_branches_1">
|
||||
<index id="52" parent="8" name="sqlite_autoindex_branches_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>branchId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="54" parent="8" name="IDX_branches_noteId_parentNoteId">
|
||||
<index id="53" parent="8" name="IDX_branches_noteId_parentNoteId">
|
||||
<ColNames>noteId
|
||||
parentNoteId</ColNames>
|
||||
</index>
|
||||
<index id="55" parent="8" name="IDX_branches_noteId">
|
||||
<ColNames>noteId</ColNames>
|
||||
</index>
|
||||
<index id="56" parent="8" name="IDX_branches_parentNoteId">
|
||||
<index id="54" parent="8" name="IDX_branches_parentNoteId">
|
||||
<ColNames>parentNoteId</ColNames>
|
||||
</index>
|
||||
<key id="57" parent="8">
|
||||
<key id="55" parent="8">
|
||||
<ColNames>branchId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="58" parent="9" name="noteId">
|
||||
<column id="56" parent="9" name="noteId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="59" parent="9" name="content">
|
||||
<column id="57" parent="9" name="content">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<DefaultExpression>NULL</DefaultExpression>
|
||||
</column>
|
||||
<column id="60" parent="9" name="hash">
|
||||
<column id="58" parent="9" name="hash">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>""</DefaultExpression>
|
||||
</column>
|
||||
<column id="61" parent="9" name="utcDateModified">
|
||||
<column id="59" parent="9" name="utcDateModified">
|
||||
<Position>4</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="62" parent="9" name="sqlite_autoindex_note_contents_1">
|
||||
<index id="60" parent="9" name="sqlite_autoindex_note_contents_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>noteId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<key id="63" parent="9">
|
||||
<key id="61" parent="9">
|
||||
<ColNames>noteId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_note_contents_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="64" parent="10" name="noteRevisionId">
|
||||
<column id="62" parent="10" name="noteRevisionId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="65" parent="10" name="content">
|
||||
<column id="63" parent="10" name="content">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
</column>
|
||||
<column id="66" parent="10" name="hash">
|
||||
<column id="64" parent="10" name="hash">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>''</DefaultExpression>
|
||||
</column>
|
||||
<column id="67" parent="10" name="utcDateModified">
|
||||
<column id="65" parent="10" name="utcDateModified">
|
||||
<Position>4</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="68" parent="10" name="sqlite_autoindex_note_revision_contents_1">
|
||||
<index id="66" parent="10" name="sqlite_autoindex_note_revision_contents_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>noteRevisionId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<key id="69" parent="10">
|
||||
<key id="67" parent="10">
|
||||
<ColNames>noteRevisionId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_note_revision_contents_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="70" parent="11" name="noteRevisionId">
|
||||
<column id="68" parent="11" name="noteRevisionId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="71" parent="11" name="noteId">
|
||||
<column id="69" parent="11" name="noteId">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="72" parent="11" name="title">
|
||||
<column id="70" parent="11" name="title">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
</column>
|
||||
<column id="73" parent="11" name="contentLength">
|
||||
<column id="71" parent="11" name="contentLength">
|
||||
<Position>4</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="74" parent="11" name="isErased">
|
||||
<column id="72" parent="11" name="isErased">
|
||||
<Position>5</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="75" parent="11" name="isProtected">
|
||||
<column id="73" parent="11" name="isProtected">
|
||||
<Position>6</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="76" parent="11" name="utcDateLastEdited">
|
||||
<column id="74" parent="11" name="utcDateLastEdited">
|
||||
<Position>7</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="77" parent="11" name="utcDateCreated">
|
||||
<column id="75" parent="11" name="utcDateCreated">
|
||||
<Position>8</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="78" parent="11" name="utcDateModified">
|
||||
<column id="76" parent="11" name="utcDateModified">
|
||||
<Position>9</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="79" parent="11" name="dateLastEdited">
|
||||
<column id="77" parent="11" name="dateLastEdited">
|
||||
<Position>10</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="80" parent="11" name="dateCreated">
|
||||
<column id="78" parent="11" name="dateCreated">
|
||||
<Position>11</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="81" parent="11" name="type">
|
||||
<column id="79" parent="11" name="type">
|
||||
<Position>12</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>''</DefaultExpression>
|
||||
</column>
|
||||
<column id="82" parent="11" name="mime">
|
||||
<column id="80" parent="11" name="mime">
|
||||
<Position>13</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>''</DefaultExpression>
|
||||
</column>
|
||||
<column id="83" parent="11" name="hash">
|
||||
<column id="81" parent="11" name="hash">
|
||||
<Position>14</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>''</DefaultExpression>
|
||||
</column>
|
||||
<index id="84" parent="11" name="sqlite_autoindex_note_revisions_1">
|
||||
<index id="82" parent="11" name="sqlite_autoindex_note_revisions_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>noteRevisionId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="85" parent="11" name="IDX_note_revisions_noteId">
|
||||
<index id="83" parent="11" name="IDX_note_revisions_noteId">
|
||||
<ColNames>noteId</ColNames>
|
||||
</index>
|
||||
<index id="86" parent="11" name="IDX_note_revisions_utcDateLastEdited">
|
||||
<index id="84" parent="11" name="IDX_note_revisions_utcDateLastEdited">
|
||||
<ColNames>utcDateLastEdited</ColNames>
|
||||
</index>
|
||||
<index id="87" parent="11" name="IDX_note_revisions_utcDateCreated">
|
||||
<index id="85" parent="11" name="IDX_note_revisions_utcDateCreated">
|
||||
<ColNames>utcDateCreated</ColNames>
|
||||
</index>
|
||||
<index id="88" parent="11" name="IDX_note_revisions_dateLastEdited">
|
||||
<index id="86" parent="11" name="IDX_note_revisions_dateLastEdited">
|
||||
<ColNames>dateLastEdited</ColNames>
|
||||
</index>
|
||||
<index id="89" parent="11" name="IDX_note_revisions_dateCreated">
|
||||
<index id="87" parent="11" name="IDX_note_revisions_dateCreated">
|
||||
<ColNames>dateCreated</ColNames>
|
||||
</index>
|
||||
<key id="90" parent="11">
|
||||
<key id="88" parent="11">
|
||||
<ColNames>noteRevisionId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="91" parent="12" name="noteId">
|
||||
<column id="89" parent="12" name="noteId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="92" parent="12" name="title">
|
||||
<column id="90" parent="12" name="title">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>"note"</DefaultExpression>
|
||||
</column>
|
||||
<column id="93" parent="12" name="contentLength">
|
||||
<column id="91" parent="12" name="contentLength">
|
||||
<Position>3</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="94" parent="12" name="isProtected">
|
||||
<column id="92" parent="12" name="isProtected">
|
||||
<Position>4</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="95" parent="12" name="type">
|
||||
<column id="93" parent="12" name="type">
|
||||
<Position>5</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>'text'</DefaultExpression>
|
||||
</column>
|
||||
<column id="96" parent="12" name="mime">
|
||||
<column id="94" parent="12" name="mime">
|
||||
<Position>6</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>'text/html'</DefaultExpression>
|
||||
</column>
|
||||
<column id="97" parent="12" name="hash">
|
||||
<column id="95" parent="12" name="hash">
|
||||
<Position>7</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>""</DefaultExpression>
|
||||
</column>
|
||||
<column id="98" parent="12" name="isDeleted">
|
||||
<column id="96" parent="12" name="isDeleted">
|
||||
<Position>8</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="99" parent="12" name="isErased">
|
||||
<column id="97" parent="12" name="isErased">
|
||||
<Position>9</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="100" parent="12" name="dateCreated">
|
||||
<column id="98" parent="12" name="dateCreated">
|
||||
<Position>10</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="101" parent="12" name="dateModified">
|
||||
<column id="99" parent="12" name="dateModified">
|
||||
<Position>11</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="102" parent="12" name="utcDateCreated">
|
||||
<column id="100" parent="12" name="utcDateCreated">
|
||||
<Position>12</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="103" parent="12" name="utcDateModified">
|
||||
<column id="101" parent="12" name="utcDateModified">
|
||||
<Position>13</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="104" parent="12" name="sqlite_autoindex_notes_1">
|
||||
<index id="102" parent="12" name="sqlite_autoindex_notes_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>noteId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="105" parent="12" name="IDX_notes_title">
|
||||
<index id="103" parent="12" name="IDX_notes_title">
|
||||
<ColNames>title</ColNames>
|
||||
</index>
|
||||
<index id="106" parent="12" name="IDX_notes_type">
|
||||
<index id="104" parent="12" name="IDX_notes_type">
|
||||
<ColNames>type</ColNames>
|
||||
</index>
|
||||
<index id="107" parent="12" name="IDX_notes_isDeleted">
|
||||
<index id="105" parent="12" name="IDX_notes_isDeleted">
|
||||
<ColNames>isDeleted</ColNames>
|
||||
</index>
|
||||
<index id="108" parent="12" name="IDX_notes_dateCreated">
|
||||
<index id="106" parent="12" name="IDX_notes_dateCreated">
|
||||
<ColNames>dateCreated</ColNames>
|
||||
</index>
|
||||
<index id="109" parent="12" name="IDX_notes_dateModified">
|
||||
<index id="107" parent="12" name="IDX_notes_dateModified">
|
||||
<ColNames>dateModified</ColNames>
|
||||
</index>
|
||||
<index id="110" parent="12" name="IDX_notes_utcDateCreated">
|
||||
<index id="108" parent="12" name="IDX_notes_utcDateCreated">
|
||||
<ColNames>utcDateCreated</ColNames>
|
||||
</index>
|
||||
<index id="111" parent="12" name="IDX_notes_utcDateModified">
|
||||
<index id="109" parent="12" name="IDX_notes_utcDateModified">
|
||||
<ColNames>utcDateModified</ColNames>
|
||||
</index>
|
||||
<key id="112" parent="12">
|
||||
<key id="110" parent="12">
|
||||
<ColNames>noteId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="113" parent="13" name="name">
|
||||
<column id="111" parent="13" name="name">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="114" parent="13" name="value">
|
||||
<column id="112" parent="13" name="value">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
</column>
|
||||
<column id="115" parent="13" name="isSynced">
|
||||
<column id="113" parent="13" name="isSynced">
|
||||
<Position>3</Position>
|
||||
<DataType>INTEGER|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="116" parent="13" name="hash">
|
||||
<column id="114" parent="13" name="hash">
|
||||
<Position>4</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>""</DefaultExpression>
|
||||
</column>
|
||||
<column id="117" parent="13" name="utcDateCreated">
|
||||
<column id="115" parent="13" name="utcDateCreated">
|
||||
<Position>5</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="118" parent="13" name="utcDateModified">
|
||||
<column id="116" parent="13" name="utcDateModified">
|
||||
<Position>6</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="119" parent="13" name="sqlite_autoindex_options_1">
|
||||
<index id="117" parent="13" name="sqlite_autoindex_options_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>name</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<key id="120" parent="13">
|
||||
<key id="118" parent="13">
|
||||
<ColNames>name</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="121" parent="14" name="noteId">
|
||||
<column id="119" parent="14" name="noteId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="122" parent="14" name="notePath">
|
||||
<column id="120" parent="14" name="notePath">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="123" parent="14" name="hash">
|
||||
<column id="121" parent="14" name="hash">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>""</DefaultExpression>
|
||||
</column>
|
||||
<column id="124" parent="14" name="utcDateCreated">
|
||||
<column id="122" parent="14" name="utcDateCreated">
|
||||
<Position>4</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="125" parent="14" name="isDeleted">
|
||||
<column id="123" parent="14" name="isDeleted">
|
||||
<Position>5</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
</column>
|
||||
<index id="126" parent="14" name="sqlite_autoindex_recent_notes_1">
|
||||
<index id="124" parent="14" name="sqlite_autoindex_recent_notes_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>noteId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<key id="127" parent="14">
|
||||
<key id="125" parent="14">
|
||||
<ColNames>noteId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="128" parent="15" name="sourceId">
|
||||
<column id="126" parent="15" name="sourceId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="129" parent="15" name="utcDateCreated">
|
||||
<column id="127" parent="15" name="utcDateCreated">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="130" parent="15" name="sqlite_autoindex_source_ids_1">
|
||||
<index id="128" parent="15" name="sqlite_autoindex_source_ids_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>sourceId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<key id="131" parent="15">
|
||||
<index id="129" parent="15" name="IDX_source_ids_utcDateCreated">
|
||||
<ColNames>utcDateCreated</ColNames>
|
||||
</index>
|
||||
<key id="130" parent="15">
|
||||
<ColNames>sourceId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="132" parent="16" name="type">
|
||||
<column id="131" parent="16" name="type">
|
||||
<Position>1</Position>
|
||||
<DataType>text|0s</DataType>
|
||||
</column>
|
||||
<column id="133" parent="16" name="name">
|
||||
<column id="132" parent="16" name="name">
|
||||
<Position>2</Position>
|
||||
<DataType>text|0s</DataType>
|
||||
</column>
|
||||
<column id="134" parent="16" name="tbl_name">
|
||||
<column id="133" parent="16" name="tbl_name">
|
||||
<Position>3</Position>
|
||||
<DataType>text|0s</DataType>
|
||||
</column>
|
||||
<column id="135" parent="16" name="rootpage">
|
||||
<column id="134" parent="16" name="rootpage">
|
||||
<Position>4</Position>
|
||||
<DataType>int|0s</DataType>
|
||||
</column>
|
||||
<column id="136" parent="16" name="sql">
|
||||
<column id="135" parent="16" name="sql">
|
||||
<Position>5</Position>
|
||||
<DataType>text|0s</DataType>
|
||||
</column>
|
||||
<column id="137" parent="17" name="name">
|
||||
<column id="136" parent="17" name="name">
|
||||
<Position>1</Position>
|
||||
</column>
|
||||
<column id="138" parent="17" name="seq">
|
||||
<column id="137" parent="17" name="seq">
|
||||
<Position>2</Position>
|
||||
</column>
|
||||
<column id="139" parent="18" name="id">
|
||||
<column id="138" parent="18" name="id">
|
||||
<Position>1</Position>
|
||||
<DataType>INTEGER|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<SequenceIdentity>1</SequenceIdentity>
|
||||
</column>
|
||||
<column id="140" parent="18" name="entityName">
|
||||
<column id="139" parent="18" name="entityName">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="141" parent="18" name="entityId">
|
||||
<column id="140" parent="18" name="entityId">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="142" parent="18" name="sourceId">
|
||||
<column id="141" parent="18" name="sourceId">
|
||||
<Position>4</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="143" parent="18" name="utcSyncDate">
|
||||
<column id="142" parent="18" name="utcSyncDate">
|
||||
<Position>5</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="144" parent="18" name="IDX_sync_entityName_entityId">
|
||||
<index id="143" parent="18" name="IDX_sync_entityName_entityId">
|
||||
<ColNames>entityName
|
||||
entityId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="145" parent="18" name="IDX_sync_utcSyncDate">
|
||||
<index id="144" parent="18" name="IDX_sync_utcSyncDate">
|
||||
<ColNames>utcSyncDate</ColNames>
|
||||
</index>
|
||||
<key id="146" parent="18">
|
||||
<key id="145" parent="18">
|
||||
<ColNames>id</ColNames>
|
||||
<Primary>1</Primary>
|
||||
</key>
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM node:12.13.0-alpine
|
||||
FROM node:12.13.1-alpine
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PKG_DIR=dist/trilium-linux-x64-server
|
||||
NODE_VERSION=12.13.0
|
||||
NODE_VERSION=12.13.1
|
||||
|
||||
if [ "$1" != "DONTCOPY" ]
|
||||
then
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
# Instance name can be used to distinguish between different instances
|
||||
instanceName=
|
||||
|
||||
# Disable automatically generating desktop icon
|
||||
# noDesktopIcon=true
|
||||
|
||||
[Network]
|
||||
# host setting is relevant only for web deployments - set the host on which the server will listen
|
||||
# host=0.0.0.0
|
||||
|
||||
BIN
Binary file not shown.
@@ -20,7 +20,7 @@ SELECT noteId, title, -1, isProtected, type, mime, hash, isDeleted, isErased, da
|
||||
DROP TABLE notes;
|
||||
ALTER TABLE notes_mig RENAME TO notes;
|
||||
|
||||
UPDATE notes SET contentLength = (SELECT COALESCE(LENGTH(content), 0) FROM note_contents WHERE note_contents.noteId = notes.noteId);
|
||||
UPDATE notes SET contentLength = COALESCE((SELECT COALESCE(LENGTH(content), 0) FROM note_contents WHERE note_contents.noteId = notes.noteId), -1);
|
||||
|
||||
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (`isDeleted`);
|
||||
CREATE INDEX `IDX_notes_title` ON `notes` (`title`);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
UPDATE attributes SET name = 'internalLink' WHERE name = 'internal-link';
|
||||
UPDATE attributes SET name = 'imageLink' WHERE name = 'image-link';
|
||||
UPDATE attributes SET name = 'relationMapLink' WHERE name = 'relation-map-link';
|
||||
@@ -0,0 +1,5 @@
|
||||
DROP INDEX IF EXISTS IDX_attributes_name_index;
|
||||
DROP INDEX IF EXISTS IDX_branches_noteId;
|
||||
|
||||
CREATE INDEX IDX_source_ids_utcDateCreated
|
||||
on source_ids (utcDateCreated);
|
||||
+2
-3
@@ -9,6 +9,8 @@ CREATE TABLE IF NOT EXISTS "source_ids" (
|
||||
`utcDateCreated` TEXT NOT NULL,
|
||||
PRIMARY KEY(`sourceId`)
|
||||
);
|
||||
CREATE INDEX IDX_source_ids_utcDateCreated
|
||||
on source_ids (utcDateCreated);
|
||||
CREATE TABLE IF NOT EXISTS "api_tokens"
|
||||
(
|
||||
apiTokenId TEXT PRIMARY KEY NOT NULL,
|
||||
@@ -47,8 +49,6 @@ CREATE INDEX `IDX_sync_utcSyncDate` ON `sync` (
|
||||
);
|
||||
CREATE INDEX IDX_attributes_name_value
|
||||
on attributes (name, value);
|
||||
CREATE INDEX IDX_attributes_name_index
|
||||
on attributes (name);
|
||||
CREATE INDEX IDX_attributes_noteId_index
|
||||
on attributes (noteId);
|
||||
CREATE INDEX IDX_attributes_value_index
|
||||
@@ -80,7 +80,6 @@ CREATE TABLE IF NOT EXISTS "branches" (
|
||||
utcDateCreated TEXT NOT NULL,
|
||||
hash TEXT DEFAULT "" NOT NULL,
|
||||
PRIMARY KEY(`branchId`));
|
||||
CREATE INDEX `IDX_branches_noteId` ON `branches` (`noteId`);
|
||||
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,
|
||||
|
||||
@@ -396,7 +396,7 @@ the backend.
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line313">line 313</a>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line394">line 394</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -534,6 +534,375 @@ the backend.
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="createDataNote"><span class="type-signature"></span>createDataNote<span class="signature">(parentNoteId, title, content)</span><span class="type-signature"> → {Promise.<{note: <a href="Note.html">Note</a>, branch: <a href="Branch.html">Branch</a>}>}</span></h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="description">
|
||||
Create data note - data in this context means object serializable to JSON. Created note will be of type 'code' and
|
||||
JSON MIME type. See also createNewNote() for more options.
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h5>Parameters:</h5>
|
||||
|
||||
|
||||
<table class="params">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
<th>Name</th>
|
||||
|
||||
|
||||
<th>Type</th>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<th class="last">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>parentNoteId</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last"></td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>title</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last"></td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>content</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">object</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last"></td>
|
||||
</tr>
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line204">line 204</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h5>Returns:</h5>
|
||||
|
||||
|
||||
|
||||
|
||||
<dl>
|
||||
<dt>
|
||||
Type
|
||||
</dt>
|
||||
<dd>
|
||||
|
||||
<span class="param-type">Promise.<{note: <a href="Note.html">Note</a>, branch: <a href="Branch.html">Branch</a>}></span>
|
||||
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="createNewNote"><span class="type-signature"></span>createNewNote<span class="signature">(params<span class="signature-attributes">opt</span>)</span><span class="type-signature"> → {Promise.<{note: <a href="Note.html">Note</a>, branch: <a href="Branch.html">Branch</a>}>}</span></h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h5>Parameters:</h5>
|
||||
|
||||
|
||||
<table class="params">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
<th>Name</th>
|
||||
|
||||
|
||||
<th>Type</th>
|
||||
|
||||
|
||||
<th>Attributes</th>
|
||||
|
||||
|
||||
|
||||
|
||||
<th class="last">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>params</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type"><a href="global.html#CreateNewNoteParams">CreateNewNoteParams</a></span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
<td class="attributes">
|
||||
|
||||
<optional><br>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last"></td>
|
||||
</tr>
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line231">line 231</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h5>Returns:</h5>
|
||||
|
||||
|
||||
<div class="param-desc">
|
||||
object contains newly created entities note and branch
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<dl>
|
||||
<dt>
|
||||
Type
|
||||
</dt>
|
||||
<dd>
|
||||
|
||||
<span class="param-type">Promise.<{note: <a href="Note.html">Note</a>, branch: <a href="Branch.html">Branch</a>}></span>
|
||||
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="createNote"><span class="type-signature"></span>createNote<span class="signature">(parentNoteId, title, content<span class="signature-attributes">opt</span>, extraOptions<span class="signature-attributes">opt</span>)</span><span class="type-signature"> → {Promise.<{note: <a href="Note.html">Note</a>, branch: <a href="Branch.html">Branch</a>}>}</span></h4>
|
||||
|
||||
|
||||
@@ -760,7 +1129,7 @@ the backend.
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line198">line 198</a>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line258">line 258</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -818,7 +1187,7 @@ the backend.
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="createNoteAndRefresh"><span class="type-signature"></span>createNoteAndRefresh<span class="signature">(parentNoteId, title, content<span class="signature-attributes">opt</span>, extraOptions<span class="signature-attributes">opt</span>)</span><span class="type-signature"> → {Promise.<{note: <a href="Note.html">Note</a>, branch: <a href="Branch.html">Branch</a>}>}</span></h4>
|
||||
<h4 class="name" id="createTextNote"><span class="type-signature"></span>createTextNote<span class="signature">(parentNoteId, title, content)</span><span class="type-signature"> → {Promise.<{note: <a href="Note.html">Note</a>, branch: <a href="Branch.html">Branch</a>}>}</span></h4>
|
||||
|
||||
|
||||
|
||||
@@ -826,7 +1195,7 @@ the backend.
|
||||
|
||||
|
||||
<div class="description">
|
||||
Creates new note according to given params and force all connected clients to refresh their tree.
|
||||
Create text note. See also createNewNote() for more options.
|
||||
</div>
|
||||
|
||||
|
||||
@@ -850,12 +1219,8 @@ the backend.
|
||||
<th>Type</th>
|
||||
|
||||
|
||||
<th>Attributes</th>
|
||||
|
||||
|
||||
|
||||
<th>Default</th>
|
||||
|
||||
|
||||
<th class="last">Description</th>
|
||||
</tr>
|
||||
@@ -879,22 +1244,10 @@ the backend.
|
||||
</td>
|
||||
|
||||
|
||||
<td class="attributes">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
<td class="default">
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
<td class="description last">create new note under this parent</td>
|
||||
<td class="description last"></td>
|
||||
</tr>
|
||||
|
||||
|
||||
@@ -914,19 +1267,7 @@ the backend.
|
||||
</td>
|
||||
|
||||
|
||||
<td class="attributes">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
<td class="default">
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
<td class="description last"></td>
|
||||
@@ -949,62 +1290,7 @@ the backend.
|
||||
</td>
|
||||
|
||||
|
||||
<td class="attributes">
|
||||
|
||||
<optional><br>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
<td class="default">
|
||||
|
||||
""
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
<td class="description last"></td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>extraOptions</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type"><a href="global.html#CreateNoteExtraOptions">CreateNoteExtraOptions</a></span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
<td class="attributes">
|
||||
|
||||
<optional><br>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
<td class="default">
|
||||
|
||||
{}
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
<td class="description last"></td>
|
||||
@@ -1048,7 +1334,7 @@ the backend.
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line211">line 211</a>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line188">line 188</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -1076,10 +1362,6 @@ the backend.
|
||||
<h5>Returns:</h5>
|
||||
|
||||
|
||||
<div class="param-desc">
|
||||
object contains newly created entities note and branch
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<dl>
|
||||
@@ -1533,7 +1815,7 @@ the backend.
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line318">line 318</a>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line399">line 399</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -1997,7 +2279,7 @@ the backend.
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line241">line 241</a>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line314">line 314</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -2765,7 +3047,7 @@ if some action needs to happen on only one specific instance.
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line260">line 260</a>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line341">line 341</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -3418,7 +3700,113 @@ if some action needs to happen on only one specific instance.
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line232">line 232</a>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line305">line 305</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h5>Returns:</h5>
|
||||
|
||||
|
||||
|
||||
|
||||
<dl>
|
||||
<dt>
|
||||
Type
|
||||
</dt>
|
||||
<dd>
|
||||
|
||||
<span class="param-type">Promise.<(<a href="Note.html">Note</a>|null)></span>
|
||||
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="getTodayNote"><span class="type-signature"></span>getTodayNote<span class="signature">()</span><span class="type-signature"> → {Promise.<(<a href="Note.html">Note</a>|null)>}</span></h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="description">
|
||||
Returns today's day note. If such note doesn't exist, it is created.
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line322">line 322</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -3596,7 +3984,7 @@ if some action needs to happen on only one specific instance.
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line251">line 251</a>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line332">line 332</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -3751,7 +4139,7 @@ if some action needs to happen on only one specific instance.
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line269">line 269</a>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line350">line 350</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -3901,7 +4289,7 @@ if some action needs to happen on only one specific instance.
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line224">line 224</a>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line297">line 297</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -4404,7 +4792,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line290">line 290</a>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line371">line 371</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -4537,7 +4925,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line276">line 276</a>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line357">line 357</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -4912,7 +5300,7 @@ transactional by default.
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line303">line 303</a>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line384">line 384</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
+2228
-68
File diff suppressed because it is too large
Load Diff
@@ -107,6 +107,10 @@ class Attribute extends Entity {
|
||||
|
||||
async beforeSaving() {
|
||||
if (!this.value) {
|
||||
if (this.type === 'relation') {
|
||||
throw new Error(`Cannot save relation ${this.name} since it does not target any note.`);
|
||||
}
|
||||
|
||||
// null value isn't allowed
|
||||
this.value = "";
|
||||
}
|
||||
@@ -137,6 +141,7 @@ class Attribute extends Entity {
|
||||
// cannot be static!
|
||||
updatePojo(pojo) {
|
||||
delete pojo.isOwned;
|
||||
delete pojo.__note;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,10 @@ class Note extends Entity {
|
||||
|
||||
/** @returns {Promise} */
|
||||
async setContent(content) {
|
||||
if (content === null || content === undefined) {
|
||||
throw new Error(`Cannot set null content to note ${this.noteId}`);
|
||||
}
|
||||
|
||||
// force updating note itself so that dateModified is represented correctly even for the content
|
||||
this.forcedChange = true;
|
||||
this.contentLength = content.length;
|
||||
@@ -220,26 +224,36 @@ class Note extends Entity {
|
||||
return null;
|
||||
}
|
||||
|
||||
async loadOwnedAttributesToCache() {
|
||||
this.__ownedAttributeCache = await repository.getEntities(`SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ?`, [this.noteId]);
|
||||
return this.__ownedAttributeCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Attribute[]>} attributes belonging to this specific note (excludes inherited attributes)
|
||||
* This method is a faster variant of getAttributes() which looks for only owned attributes.
|
||||
* Use when inheritance is not needed and/or in batch/performance sensitive operations.
|
||||
*
|
||||
* This method can be significantly faster than the getAttributes()
|
||||
* @param {string} [type] - (optional) attribute type to filter
|
||||
* @param {string} [name] - (optional) attribute name to filter
|
||||
* @returns {Promise<Attribute[]>} note's "owned" attributes - excluding inherited ones
|
||||
*/
|
||||
async getOwnedAttributes(type, name) {
|
||||
let query = `SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ?`;
|
||||
const params = [this.noteId];
|
||||
|
||||
if (type) {
|
||||
query += ` AND type = ?`;
|
||||
params.push(type);
|
||||
if (!this.__ownedAttributeCache) {
|
||||
await this.loadOwnedAttributesToCache();
|
||||
}
|
||||
|
||||
if (name) {
|
||||
query += ` AND name = ?`;
|
||||
params.push(name);
|
||||
if (type && name) {
|
||||
return this.__ownedAttributeCache.filter(attr => attr.type === type && attr.name === name);
|
||||
}
|
||||
else if (type) {
|
||||
return this.__ownedAttributeCache.filter(attr => attr.type === type);
|
||||
}
|
||||
else if (name) {
|
||||
return this.__ownedAttributeCache.filter(attr => attr.name === name);
|
||||
}
|
||||
else {
|
||||
return this.__ownedAttributeCache.slice();
|
||||
}
|
||||
|
||||
return await repository.getEntities(query, params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,19 +275,26 @@ class Note extends Entity {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [name] - attribute name to filter
|
||||
* @param {string} [type] - (optional) attribute type to filter
|
||||
* @param {string} [name] - (optional) attribute name to filter
|
||||
* @returns {Promise<Attribute[]>} all note's attributes, including inherited ones
|
||||
*/
|
||||
async getAttributes(name) {
|
||||
async getAttributes(type, name) {
|
||||
if (!this.__attributeCache) {
|
||||
await this.loadAttributesToCache();
|
||||
}
|
||||
|
||||
if (name) {
|
||||
if (type && name) {
|
||||
return this.__attributeCache.filter(attr => attr.type === type && attr.name === name);
|
||||
}
|
||||
else if (type) {
|
||||
return this.__attributeCache.filter(attr => attr.type === type);
|
||||
}
|
||||
else if (name) {
|
||||
return this.__attributeCache.filter(attr => attr.name === name);
|
||||
}
|
||||
else {
|
||||
return this.__attributeCache;
|
||||
return this.__attributeCache.slice();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +303,15 @@ class Note extends Entity {
|
||||
* @returns {Promise<Attribute[]>} all note's labels (attributes with type label), including inherited ones
|
||||
*/
|
||||
async getLabels(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL);
|
||||
return await this.getAttributes(LABEL, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [name] - label name to filter
|
||||
* @returns {Promise<Attribute[]>} all note's labels (attributes with type label), excluding inherited ones
|
||||
*/
|
||||
async getOwnedLabels(name) {
|
||||
return await this.getOwnedAttributes(LABEL, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,7 +319,7 @@ class Note extends Entity {
|
||||
* @returns {Promise<Attribute[]>} all note's label definitions, including inherited ones
|
||||
*/
|
||||
async getLabelDefinitions(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL_DEFINITION);
|
||||
return await this.getAttributes(LABEL_DEFINITION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,7 +327,15 @@ class Note extends Entity {
|
||||
* @returns {Promise<Attribute[]>} all note's relations (attributes with type relation), including inherited ones
|
||||
*/
|
||||
async getRelations(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION);
|
||||
return await this.getAttributes(RELATION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [name] - relation name to filter
|
||||
* @returns {Promise<Attribute[]>} all note's relations (attributes with type relation), excluding inherited ones
|
||||
*/
|
||||
async getOwnedRelations(name) {
|
||||
return await this.getOwnedAttributes(RELATION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,7 +358,7 @@ class Note extends Entity {
|
||||
* @returns {Promise<Attribute[]>} all note's relation definitions including inherited ones
|
||||
*/
|
||||
async getRelationDefinitions(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION_DEFINITION);
|
||||
return await this.getAttributes(RELATION_DEFINITION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -330,6 +367,7 @@ class Note extends Entity {
|
||||
*/
|
||||
invalidateAttributeCache() {
|
||||
this.__attributeCache = null;
|
||||
this.__ownedAttributeCache = null;
|
||||
}
|
||||
|
||||
/** @returns {Promise<void>} */
|
||||
@@ -339,11 +377,10 @@ class Note extends Entity {
|
||||
tree(noteId, level) AS (
|
||||
SELECT ?, 0
|
||||
UNION
|
||||
SELECT branches.parentNoteId, tree.level + 1 FROM branches
|
||||
SELECT branches.parentNoteId, tree.level + 1
|
||||
FROM branches
|
||||
JOIN tree ON branches.noteId = tree.noteId
|
||||
JOIN notes ON notes.noteId = branches.parentNoteId
|
||||
WHERE notes.isDeleted = 0
|
||||
AND branches.isDeleted = 0
|
||||
WHERE branches.isDeleted = 0
|
||||
),
|
||||
treeWithAttrs(noteId, level) AS (
|
||||
SELECT * FROM tree
|
||||
@@ -362,6 +399,11 @@ class Note extends Entity {
|
||||
// we order by noteId so that attributes from same note stay together. Actual noteId ordering doesn't matter.
|
||||
|
||||
const filteredAttributes = attributes.filter((attr, index) => {
|
||||
// if this exact attribute already appears then don't include it (can happen via cloning)
|
||||
if (attributes.findIndex(it => it.attributeId === attr.attributeId) !== index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (attr.isDefinition()) {
|
||||
const firstDefinitionIndex = attributes.findIndex(el => el.type === attr.type && el.name === attr.name);
|
||||
|
||||
@@ -405,6 +447,15 @@ class Note extends Entity {
|
||||
return !!await this.getAttribute(type, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type - attribute type (label, relation, etc.)
|
||||
* @param {string} name - attribute name
|
||||
* @returns {Promise<boolean>} true if note has an attribute with given type and name (excluding inherited)
|
||||
*/
|
||||
async hasOwnedAttribute(type, name) {
|
||||
return !!await this.getOwnedAttribute(type, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type - attribute type (label, relation, etc.)
|
||||
* @param {string} name - attribute name
|
||||
@@ -419,7 +470,7 @@ class Note extends Entity {
|
||||
/**
|
||||
* @param {string} type - attribute type (label, relation, etc.)
|
||||
* @param {string} name - attribute name
|
||||
* @returns {Promise<string>} attribute value of given type and name or null if no such attribute exists.
|
||||
* @returns {Promise<string|null>} attribute value of given type and name or null if no such attribute exists.
|
||||
*/
|
||||
async getAttributeValue(type, name) {
|
||||
const attr = await this.getAttribute(type, name);
|
||||
@@ -427,6 +478,17 @@ class Note extends Entity {
|
||||
return attr ? attr.value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type - attribute type (label, relation, etc.)
|
||||
* @param {string} name - attribute name
|
||||
* @returns {Promise<string|null>} attribute value of given type and name or null if no such attribute exists.
|
||||
*/
|
||||
async getOwnedAttributeValue(type, name) {
|
||||
const attr = await this.getOwnedAttribute(type, name);
|
||||
|
||||
return attr ? attr.value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on enabled, attribute is either set or removed.
|
||||
*
|
||||
@@ -454,7 +516,7 @@ class Note extends Entity {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async setAttribute(type, name, value) {
|
||||
const attributes = await this.getOwnedAttributes();
|
||||
const attributes = await this.loadOwnedAttributesToCache();
|
||||
let attr = attributes.find(attr => attr.type === type && attr.name === name);
|
||||
|
||||
if (attr) {
|
||||
@@ -488,7 +550,7 @@ class Note extends Entity {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async removeAttribute(type, name, value) {
|
||||
const attributes = await this.getOwnedAttributes();
|
||||
const attributes = await this.loadOwnedAttributesToCache();
|
||||
|
||||
for (const attribute of attributes) {
|
||||
if (attribute.type === type && (value === undefined || value === attribute.value)) {
|
||||
@@ -500,42 +562,104 @@ class Note extends Entity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise<Attribute>}
|
||||
*/
|
||||
async addAttribute(type, name, value = "") {
|
||||
const attr = new Attribute({
|
||||
noteId: this.noteId,
|
||||
type: type,
|
||||
name: name,
|
||||
value: value
|
||||
});
|
||||
|
||||
await attr.save();
|
||||
|
||||
this.invalidateAttributeCache();
|
||||
|
||||
return attr;
|
||||
}
|
||||
|
||||
async addLabel(name, value = "") {
|
||||
return await this.addAttribute(LABEL, name, value);
|
||||
}
|
||||
|
||||
async addRelation(name, targetNoteId) {
|
||||
return await this.addAttribute(RELATION, name, targetNoteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - label name
|
||||
* @returns {Promise<boolean>} true if label exists (including inherited)
|
||||
*/
|
||||
async hasLabel(name) { return await this.hasAttribute(LABEL, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - label name
|
||||
* @returns {Promise<boolean>} true if label exists (excluding inherited)
|
||||
*/
|
||||
async hasOwnedLabel(name) { return await this.hasOwnedAttribute(LABEL, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - relation name
|
||||
* @returns {Promise<boolean>} true if relation exists (including inherited)
|
||||
*/
|
||||
async hasRelation(name) { return await this.hasAttribute(RELATION, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - relation name
|
||||
* @returns {Promise<boolean>} true if relation exists (excluding inherited)
|
||||
*/
|
||||
async hasOwnedRelation(name) { return await this.hasOwnedAttribute(RELATION, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - label name
|
||||
* @returns {Promise<Attribute>} label if it exists, null otherwise
|
||||
* @returns {Promise<Attribute|null>} label if it exists, null otherwise
|
||||
*/
|
||||
async getLabel(name) { return await this.getAttribute(LABEL, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - label name
|
||||
* @returns {Promise<Attribute|null>} label if it exists, null otherwise
|
||||
*/
|
||||
async getOwnedLabel(name) { return await this.getOwnedAttribute(LABEL, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - relation name
|
||||
* @returns {Promise<Attribute>} relation if it exists, null otherwise
|
||||
* @returns {Promise<Attribute|null>} relation if it exists, null otherwise
|
||||
*/
|
||||
async getRelation(name) { return await this.getAttribute(RELATION, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - relation name
|
||||
* @returns {Promise<Attribute|null>} relation if it exists, null otherwise
|
||||
*/
|
||||
async getOwnedRelation(name) { return await this.getOwnedAttribute(RELATION, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - label name
|
||||
* @returns {Promise<string>} label value if label exists, null otherwise
|
||||
* @returns {Promise<string|null>} label value if label exists, null otherwise
|
||||
*/
|
||||
async getLabelValue(name) { return await this.getAttributeValue(LABEL, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - label name
|
||||
* @returns {Promise<string|null>} label value if label exists, null otherwise
|
||||
*/
|
||||
async getOwnedLabelValue(name) { return await this.getOwnedAttributeValue(LABEL, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - relation name
|
||||
* @returns {Promise<string>} relation value if relation exists, null otherwise
|
||||
* @returns {Promise<string|null>} relation value if relation exists, null otherwise
|
||||
*/
|
||||
async getRelationValue(name) { return await this.getAttributeValue(RELATION, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - relation name
|
||||
* @returns {Promise<string|null>} relation value if relation exists, null otherwise
|
||||
*/
|
||||
async getOwnedRelationValue(name) { return await this.getOwnedAttributeValue(RELATION, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {Promise<Note>|null} target note of the relation or null (if target is empty or note was not found)
|
||||
@@ -546,6 +670,16 @@ class Note extends Entity {
|
||||
return relation ? await repository.getNote(relation.value) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {Promise<Note>|null} target note of the relation or null (if target is empty or note was not found)
|
||||
*/
|
||||
async getOwnedRelationTarget(name) {
|
||||
const relation = await this.getOwnedRelation(name);
|
||||
|
||||
return relation ? await repository.getNote(relation.value) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on enabled, label is either set or removed.
|
||||
*
|
||||
@@ -699,7 +833,7 @@ class Note extends Entity {
|
||||
WHERE noteId = ? AND
|
||||
isDeleted = 0 AND
|
||||
type = 'relation' AND
|
||||
name IN ('internal-link', 'image-link', 'relation-map-link')`, [this.noteId]);
|
||||
name IN ('internalLink', 'imageLink', 'relationMapLink')`, [this.noteId]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -776,6 +910,16 @@ class Note extends Entity {
|
||||
return notePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ancestorNoteId
|
||||
* @return {Promise<boolean>} - true if ancestorNoteId occurs in at least one of the note's paths
|
||||
*/
|
||||
async isDescendantOfNote(ancestorNoteId) {
|
||||
const notePaths = await this.getAllNotePaths();
|
||||
|
||||
return notePaths.some(path => path.includes(ancestorNoteId));
|
||||
}
|
||||
|
||||
beforeSaving() {
|
||||
if (!this.isDeleted) {
|
||||
this.isDeleted = false;
|
||||
@@ -816,6 +960,7 @@ class Note extends Entity {
|
||||
delete pojo.isContentAvailable;
|
||||
delete pojo.__attributeCache;
|
||||
delete pojo.content;
|
||||
/** zero references to contentHash, probably can be removed */
|
||||
delete pojo.contentHash;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,313 @@
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="CreateNewNoteParams">CreateNewNoteParams</h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h5>Type:</h5>
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
<span class="param-type">object</span>
|
||||
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h5 class="subsection-title">Properties:</h5>
|
||||
|
||||
|
||||
|
||||
<table class="props">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
<th>Name</th>
|
||||
|
||||
|
||||
<th>Type</th>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<th class="last">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>parentNoteId</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last">MANDATORY</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>title</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last">MANDATORY</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>content</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
|
|
||||
|
||||
<span class="param-type">buffer</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last">MANDATORY</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>type</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last">text, code, file, image, search, book, relation-map - MANDATORY</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>mime</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last">value is derived from default mimes for type</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>isProtected</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">boolean</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last">default is false</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>isExpanded</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">boolean</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last">default is false</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>prefix</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last">default is empty string</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>notePosition</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">int</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last">default is last existing notePosition in a parent + 10</td>
|
||||
</tr>
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line212">line 212</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="CreateNoteAttribute">CreateNoteAttribute</h4>
|
||||
|
||||
|
||||
@@ -290,6 +597,194 @@
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="CreateNoteAttribute">CreateNoteAttribute</h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h5>Type:</h5>
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
<span class="param-type">object</span>
|
||||
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h5 class="subsection-title">Properties:</h5>
|
||||
|
||||
|
||||
|
||||
<table class="props">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
<th>Name</th>
|
||||
|
||||
|
||||
<th>Type</th>
|
||||
|
||||
|
||||
<th>Attributes</th>
|
||||
|
||||
|
||||
|
||||
|
||||
<th class="last">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>type</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
<td class="attributes">
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last">attribute type - label, relation etc.</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>name</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
<td class="attributes">
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last">attribute name</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>value</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
<td class="attributes">
|
||||
|
||||
<optional><br>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last">attribute value</td>
|
||||
</tr>
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line233">line 233</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="CreateNoteExtraOptions">CreateNoteExtraOptions</h4>
|
||||
|
||||
|
||||
@@ -558,7 +1053,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line180">line 180</a>
|
||||
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line240">line 240</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
@@ -198,6 +198,66 @@ function BackendScriptApi(currentNote, apiParams) {
|
||||
*/
|
||||
this.toggleNoteInParent = cloningService.toggleNoteInParent;
|
||||
|
||||
/**
|
||||
* @typedef {object} CreateNoteAttribute
|
||||
* @property {string} type - attribute type - label, relation etc.
|
||||
* @property {string} name - attribute name
|
||||
* @property {string} [value] - attribute value
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create text note. See also createNewNote() for more options.
|
||||
*
|
||||
* @param {string} parentNoteId
|
||||
* @param {string} title
|
||||
* @param {string} content
|
||||
* @return {Promise<{note: Note, branch: Branch}>}
|
||||
*/
|
||||
this.createTextNote = async (parentNoteId, title, content = '') => await noteService.createNewNote({
|
||||
parentNoteId,
|
||||
title,
|
||||
content,
|
||||
type: 'text'
|
||||
});
|
||||
|
||||
/**
|
||||
* Create data note - data in this context means object serializable to JSON. Created note will be of type 'code' and
|
||||
* JSON MIME type. See also createNewNote() for more options.
|
||||
*
|
||||
* @param {string} parentNoteId
|
||||
* @param {string} title
|
||||
* @param {object} content
|
||||
* @return {Promise<{note: Note, branch: Branch}>}
|
||||
*/
|
||||
this.createDataNote = async (parentNoteId, title, content = {}) => await noteService.createNewNote({
|
||||
parentNoteId,
|
||||
title,
|
||||
content: JSON.stringify(content),
|
||||
type: 'code',
|
||||
mime: 'application/json'
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef {object} CreateNewNoteParams
|
||||
* @property {string} parentNoteId - MANDATORY
|
||||
* @property {string} title - MANDATORY
|
||||
* @property {string|buffer} content - MANDATORY
|
||||
* @property {string} type - text, code, file, image, search, book, relation-map - MANDATORY
|
||||
* @property {string} mime - value is derived from default mimes for type
|
||||
* @property {boolean} isProtected - default is false
|
||||
* @property {boolean} isExpanded - default is false
|
||||
* @property {string} prefix - default is empty string
|
||||
* @property {int} notePosition - default is last existing notePosition in a parent + 10
|
||||
*/
|
||||
|
||||
/**
|
||||
* @method
|
||||
*
|
||||
* @param {CreateNewNoteParams} [params]
|
||||
* @returns {Promise<{note: Note, branch: Branch}>} object contains newly created entities note and branch
|
||||
*/
|
||||
this.createNewNote = noteService.createNewNote;
|
||||
|
||||
/**
|
||||
* @typedef {object} CreateNoteAttribute
|
||||
* @property {string} type - attribute type - label, relation etc.
|
||||
@@ -223,25 +283,38 @@ function BackendScriptApi(currentNote, apiParams) {
|
||||
* @param {CreateNoteExtraOptions} [extraOptions={}]
|
||||
* @returns {Promise<{note: Note, branch: Branch}>} object contains newly created entities note and branch
|
||||
*/
|
||||
this.createNote = noteService.createNote;
|
||||
this.createNote = async (parentNoteId, title, content = "", extraOptions= {}) => {
|
||||
extraOptions.parentNoteId = parentNoteId;
|
||||
extraOptions.title = title;
|
||||
|
||||
/**
|
||||
* Creates new note according to given params and force all connected clients to refresh their tree.
|
||||
*
|
||||
* @method
|
||||
*
|
||||
* @param {string} parentNoteId - create new note under this parent
|
||||
* @param {string} title
|
||||
* @param {string} [content=""]
|
||||
* @param {CreateNoteExtraOptions} [extraOptions={}]
|
||||
* @returns {Promise<{note: Note, branch: Branch}>} object contains newly created entities note and branch
|
||||
*/
|
||||
this.createNoteAndRefresh = async function(parentNoteId, title, content, extraOptions) {
|
||||
const ret = await noteService.createNote(parentNoteId, title, content, extraOptions);
|
||||
const parentNote = await repository.getNote(parentNoteId);
|
||||
|
||||
ws.refreshTree();
|
||||
|
||||
return ret;
|
||||
// code note type can be inherited, otherwise text is default
|
||||
extraOptions.type = parentNote.type === 'code' ? 'code' : 'text';
|
||||
extraOptions.mime = parentNote.type === 'code' ? parentNote.mime : 'text/html';
|
||||
|
||||
if (extraOptions.json) {
|
||||
extraOptions.content = JSON.stringify(content || {}, null, '\t');
|
||||
extraOptions.type = 'code';
|
||||
extraOptions.mime = 'application/json';
|
||||
}
|
||||
else {
|
||||
extraOptions.content = content;
|
||||
}
|
||||
|
||||
const {note, branch} = await noteService.createNewNote(extraOptions);
|
||||
|
||||
for (const attr of extraOptions.attributes || []) {
|
||||
await attributeService.createAttribute({
|
||||
noteId: note.noteId,
|
||||
type: attr.type,
|
||||
name: attr.name,
|
||||
value: attr.value,
|
||||
isInheritable: !!attr.isInheritable
|
||||
});
|
||||
}
|
||||
|
||||
return {note, branch};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -268,6 +341,14 @@ function BackendScriptApi(currentNote, apiParams) {
|
||||
*/
|
||||
this.getDateNote = dateNoteService.getDateNote;
|
||||
|
||||
/**
|
||||
* Returns today's day note. If such note doesn't exist, it is created.
|
||||
*
|
||||
* @method
|
||||
* @returns {Promise<Note|null>}
|
||||
*/
|
||||
this.getTodayNote = dateNoteService.getTodayNote;
|
||||
|
||||
/**
|
||||
* Returns note for the first date of the week of the given date.
|
||||
*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,280 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Class: KeyboardActions</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Class: KeyboardActions</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
|
||||
<header>
|
||||
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>KeyboardActions<span class="signature">()</span><span class="type-signature"></span></h2>
|
||||
|
||||
<div class="class-description">blaa vlaa</div>
|
||||
|
||||
|
||||
</header>
|
||||
|
||||
<article>
|
||||
<div class="container-overview">
|
||||
|
||||
|
||||
|
||||
|
||||
<h2>Constructor</h2>
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="KeyboardActions"><span class="type-signature"></span>new KeyboardActions<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_keyboard_actions.js.html">services/keyboard_action.js</a>, <a href="services_keyboard_actions.js.html#line5">line 5</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h3 class="subsection-title">Members</h3>
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="JUMP_TO"><span class="type-signature"></span>JUMP_TO<span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h5 class="subsection-title">Properties:</h5>
|
||||
|
||||
|
||||
|
||||
<table class="props">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
|
||||
<th>Type</th>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<th class="last">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last"></td>
|
||||
</tr>
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_keyboard_actions.js.html">services/keyboard_action.js</a>, <a href="services_keyboard_actions.js.html#line7">line 7</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</article>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="KeyboardActions.html">KeyboardActions</a></li><li><a href="NoteFull.html">NoteFull</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.3</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1220,7 +1220,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line220">line 220</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line227">line 227</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -1278,7 +1278,7 @@
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="getAttributes"><span class="type-signature">(async) </span>getAttributes<span class="signature">(name<span class="signature-attributes">opt</span>)</span><span class="type-signature"> → {Promise.<Array.<<a href="Attribute.html">Attribute</a>>>}</span></h4>
|
||||
<h4 class="name" id="getAttributes"><span class="type-signature">(async) </span>getAttributes<span class="signature">(type<span class="signature-attributes">opt</span>, name<span class="signature-attributes">opt</span>)</span><span class="type-signature"> → {Promise.<Array.<<a href="Attribute.html">Attribute</a>>>}</span></h4>
|
||||
|
||||
|
||||
|
||||
@@ -1318,6 +1318,39 @@
|
||||
<tbody>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>type</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
<td class="attributes">
|
||||
|
||||
<optional><br>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last">(optional) attribute type to filter</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>name</code></td>
|
||||
@@ -1346,7 +1379,7 @@
|
||||
|
||||
|
||||
|
||||
<td class="description last">attribute name to filter</td>
|
||||
<td class="description last">(optional) attribute name to filter</td>
|
||||
</tr>
|
||||
|
||||
|
||||
@@ -1387,7 +1420,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line160">line 160</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line161">line 161</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -1415,6 +1448,10 @@
|
||||
<h5>Returns:</h5>
|
||||
|
||||
|
||||
<div class="param-desc">
|
||||
all note's attributes, including inherited ones
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<dl>
|
||||
@@ -1561,7 +1598,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line231">line 231</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line238">line 238</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -2124,7 +2161,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line253">line 253</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line260">line 260</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -2291,7 +2328,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line186">line 186</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line193">line 193</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -2458,7 +2495,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line178">line 178</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line185">line 185</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -2613,7 +2650,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line265">line 265</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line272">line 272</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -2972,7 +3009,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line259">line 259</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line266">line 266</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -3139,7 +3176,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line202">line 202</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line209">line 209</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -3306,7 +3343,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line194">line 194</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line201">line 201</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -3461,7 +3498,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line277">line 277</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line284">line 284</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -3631,7 +3668,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line287">line 287</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line294">line 294</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -3782,7 +3819,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line271">line 271</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line278">line 278</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -3892,7 +3929,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line311">line 311</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line318">line 318</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -4066,7 +4103,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line211">line 211</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line218">line 218</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -4323,7 +4360,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line241">line 241</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line248">line 248</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -4478,7 +4515,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line247">line 247</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line254">line 254</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -4536,7 +4573,7 @@
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="invalidateAttributeCache"><span class="type-signature"></span>invalidateAttributeCache<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
<h4 class="name" id="invalidate__attributeCache"><span class="type-signature"></span>invalidate__attributeCache<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
@@ -4589,7 +4626,7 @@ Cache is note instance scoped.
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line302">line 302</a>
|
||||
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line309">line 309</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
@@ -182,20 +182,27 @@ class NoteShort {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [name] - attribute name to filter
|
||||
* @returns {Promise<Attribute[]>}
|
||||
* @param {string} [type] - (optional) attribute type to filter
|
||||
* @param {string} [name] - (optional) attribute name to filter
|
||||
* @returns {Promise<Attribute[]>} all note's attributes, including inherited ones
|
||||
*/
|
||||
async getAttributes(name) {
|
||||
if (!this.attributeCache) {
|
||||
this.attributeCache = (await server.get('notes/' + this.noteId + '/attributes'))
|
||||
async getAttributes(type, name) {
|
||||
if (!this.__attributeCache) {
|
||||
this.__attributeCache = (await server.get('notes/' + this.noteId + '/attributes'))
|
||||
.map(attrRow => new Attribute(this.treeCache, attrRow));
|
||||
}
|
||||
|
||||
if (name) {
|
||||
return this.attributeCache.filter(attr => attr.name === name);
|
||||
if (type && name) {
|
||||
return this.__attributeCache.filter(attr => attr.type === type && attr.name === name);
|
||||
}
|
||||
else if (type) {
|
||||
return this.__attributeCache.filter(attr => attr.type === type);
|
||||
}
|
||||
else if (name) {
|
||||
return this.__attributeCache.filter(attr => attr.name === name);
|
||||
}
|
||||
else {
|
||||
return this.attributeCache;
|
||||
return this.__attributeCache.slice();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +211,7 @@ class NoteShort {
|
||||
* @returns {Promise<Attribute[]>} all note's labels (attributes with type label), including inherited ones
|
||||
*/
|
||||
async getLabels(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL);
|
||||
return await this.getAttributes(LABEL, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,7 +219,7 @@ class NoteShort {
|
||||
* @returns {Promise<Attribute[]>} all note's label definitions, including inherited ones
|
||||
*/
|
||||
async getLabelDefinitions(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL_DEFINITION);
|
||||
return await this.getAttributes(LABEL_DEFINITION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,7 +227,7 @@ class NoteShort {
|
||||
* @returns {Promise<Attribute[]>} all note's relations (attributes with type relation), including inherited ones
|
||||
*/
|
||||
async getRelations(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION);
|
||||
return await this.getAttributes(RELATION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,7 +235,7 @@ class NoteShort {
|
||||
* @returns {Promise<Attribute[]>} all note's relation definitions including inherited ones
|
||||
*/
|
||||
async getRelationDefinitions(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION_DEFINITION);
|
||||
return await this.getAttributes(RELATION_DEFINITION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -327,8 +334,8 @@ class NoteShort {
|
||||
* Clear note's attributes cache to force fresh reload for next attribute request.
|
||||
* Cache is note instance scoped.
|
||||
*/
|
||||
invalidateAttributeCache() {
|
||||
this.attributeCache = null;
|
||||
invalidate__attributeCache() {
|
||||
this.__attributeCache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -349,7 +356,7 @@ class NoteShort {
|
||||
const dto = Object.assign({}, this);
|
||||
delete dto.treeCache;
|
||||
delete dto.archived;
|
||||
delete dto.attributeCache;
|
||||
delete dto.__attributeCache;
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Class: exports</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Class: exports</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
|
||||
<header>
|
||||
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>exports<span class="signature">()</span><span class="type-signature"></span></h2>
|
||||
|
||||
<div class="class-description">blaa vlaa</div>
|
||||
|
||||
|
||||
</header>
|
||||
|
||||
<article>
|
||||
<div class="container-overview">
|
||||
|
||||
|
||||
|
||||
|
||||
<h2>Constructor</h2>
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="exports"><span class="type-signature"></span>new exports<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_keyboard_actions.js.html">services/keyboard_action.js</a>, <a href="services_keyboard_actions.js.html#line5">line 5</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h3 class="subsection-title">Members</h3>
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="JUMP_TO"><span class="type-signature"></span>JUMP_TO<span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h5 class="subsection-title">Properties:</h5>
|
||||
|
||||
|
||||
|
||||
<table class="props">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
|
||||
<th>Type</th>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<th class="last">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last"></td>
|
||||
</tr>
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_keyboard_actions.js.html">services/keyboard_action.js</a>, <a href="services_keyboard_actions.js.html#line7">line 7</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</article>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="module.exports.html">exports</a></li><li><a href="NoteFull.html">NoteFull</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.3</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,280 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: services/keyboard_action.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: services/keyboard_action.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* blaa vlaa
|
||||
*/
|
||||
class KeyboardAction {
|
||||
constructor(params) {
|
||||
this.optionName = params.optionName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open "Jump to note" dialog
|
||||
* @static
|
||||
*/
|
||||
KeyboardAction.JumpToNote = new KeyboardAction({
|
||||
optionName: "JumpToNote",
|
||||
defaultShortcuts: "mod+j",
|
||||
description: 'Open "Jump to note" dialog'
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.MarkdownToHTML = new KeyboardAction({
|
||||
optionName: "MarkdownToHTML",
|
||||
defaultShortcuts: "mod+return"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.NewTab = new KeyboardAction({
|
||||
optionName: "NewTab",
|
||||
defaultShortcuts: "mod+t"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.CloseTab = new KeyboardAction({
|
||||
optionName: "CloseTab",
|
||||
defaultShortcuts: "mod+w"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.NextTab = new KeyboardAction({
|
||||
optionName: "NextTab",
|
||||
defaultShortcuts: "mod+tab"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.PreviousTab = new KeyboardAction({
|
||||
optionName: "PreviousTab",
|
||||
defaultShortcuts: "mod+shift+tab"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.CreateNoteAfter = new KeyboardAction({
|
||||
optionName: "CreateNoteAfter",
|
||||
defaultShortcuts: "mod+o"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.CreateNoteInto = new KeyboardAction({
|
||||
optionName: "CreateNoteInto",
|
||||
defaultShortcuts: "mod+p"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.ScrollToActiveNote = new KeyboardAction({
|
||||
optionName: "ScrollToActiveNote",
|
||||
defaultShortcuts: "mod+."
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.CollapseTree = new KeyboardAction({
|
||||
optionName: "CollapseTree",
|
||||
defaultShortcuts: "alt+c"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.RunSQL = new KeyboardAction({
|
||||
optionName: "RunSQL",
|
||||
defaultShortcuts: "mod+return"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.FocusNote = new KeyboardAction({
|
||||
optionName: "FocusNote",
|
||||
defaultShortcuts: "return"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.RunCurrentNote = new KeyboardAction({
|
||||
optionName: "RunCurrentNote",
|
||||
defaultShortcuts: "mod+return"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.ClipboardCopy = new KeyboardAction({
|
||||
optionName: "ClipboardCopy",
|
||||
defaultShortcuts: "mod+c"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.ClipboardPaste = new KeyboardAction({
|
||||
optionName: "ClipboardPaste",
|
||||
defaultShortcuts: "mod+v"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.ClipboardCut = new KeyboardAction({
|
||||
optionName: "ClipboardCut",
|
||||
defaultShortcuts: "mod+x"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.SelectAllNotesInParent = new KeyboardAction({
|
||||
optionName: "SelectAllNotesInParent",
|
||||
defaultShortcuts: "mod+a"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.Undo = new KeyboardAction({
|
||||
optionName: "Undo",
|
||||
defaultShortcuts: "mod+z"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.Redo = new KeyboardAction({
|
||||
optionName: "Redo",
|
||||
defaultShortcuts: "mod+y"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.AddLinkToText = new KeyboardAction({
|
||||
optionName: "AddLinkToText",
|
||||
defaultShortcuts: "mod+l"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.CloneNotesTo = new KeyboardAction({
|
||||
optionName: "CloneNotesTo",
|
||||
defaultShortcuts: "mod+shift+c"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.MoveNotesTo = new KeyboardAction({
|
||||
optionName: "MoveNotesTo",
|
||||
defaultShortcuts: "mod+shift+c"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.SearchNotes = new KeyboardAction({
|
||||
optionName: "SearchNotes",
|
||||
defaultShortcuts: "mod+s"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.ShowAttributes = new KeyboardAction({
|
||||
optionName: "ShowAttributes",
|
||||
defaultShortcuts: "alt+a"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.ShowHelp = new KeyboardAction({
|
||||
optionName: "ShowHelp",
|
||||
defaultShortcuts: "f1"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.OpenSQLConsole = new KeyboardAction({
|
||||
optionName: "OpenSQLConsole",
|
||||
defaultShortcuts: "alt+o"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.BackInNoteHistory = new KeyboardAction({
|
||||
optionName: "BackInNoteHistory",
|
||||
defaultShortcuts: "alt+left"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.ForwardInNoteHistory = new KeyboardAction({
|
||||
optionName: "ForwardInNoteHistory",
|
||||
defaultShortcuts: "alt+right"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.ToggleZenMode = new KeyboardAction({
|
||||
optionName: "ToggleZenMode",
|
||||
defaultShortcuts: "alt+m"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.InsertDateTime = new KeyboardAction({
|
||||
optionName: "InsertDateTime",
|
||||
defaultShortcuts: "alt+t"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.ReloadApp = new KeyboardAction({
|
||||
optionName: "ReloadApp",
|
||||
defaultShortcuts: ["f5", "mod+r"]
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.OpenDevTools = new KeyboardAction({
|
||||
optionName: "OpenDevTools",
|
||||
defaultShortcuts: "mod+shift+i"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.FindInText = new KeyboardAction({
|
||||
optionName: "FindInText",
|
||||
defaultShortcuts: "mod+f"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.ToggleFullscreen = new KeyboardAction({
|
||||
optionName: "ToggleFullscreen",
|
||||
defaultShortcuts: "f11"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.ZoomOut = new KeyboardAction({
|
||||
optionName: "ZoomOut",
|
||||
defaultShortcuts: "mod+-"
|
||||
});
|
||||
|
||||
/** @static */
|
||||
KeyboardAction.ZoomIn = new KeyboardAction({
|
||||
optionName: "ZoomIn",
|
||||
defaultShortcuts: "mod+="
|
||||
});
|
||||
|
||||
export default KeyboardAction;</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="KeyboardAction.html">KeyboardAction</a></li><li><a href="NoteFull.html">NoteFull</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.3</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: services/keyboard_action.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: services/keyboard_action.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* blaa vlaa
|
||||
*/
|
||||
class KeyboardActions {
|
||||
constructor() {
|
||||
/** @property {string} */
|
||||
this.JUMP_TO = "";
|
||||
}
|
||||
}
|
||||
|
||||
export default KeyboardActions;</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="KeyboardActions.html">KeyboardActions</a></li><li><a href="NoteFull.html">NoteFull</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.3</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
||||
+34
-10
@@ -8,6 +8,7 @@ const cls = require('./src/services/cls');
|
||||
const url = require("url");
|
||||
const port = require('./src/services/port');
|
||||
const env = require('./src/services/env');
|
||||
const keyboardActionsService = require('./src/services/keyboard_actions');
|
||||
const appIconService = require('./src/services/app_icon');
|
||||
const windowStateKeeper = require('electron-window-state');
|
||||
|
||||
@@ -95,21 +96,44 @@ app.on('activate', () => {
|
||||
}
|
||||
});
|
||||
|
||||
async function registerGlobalShortcuts() {
|
||||
await sqlInit.dbReady;
|
||||
|
||||
const allActions = await keyboardActionsService.getKeyboardActions();
|
||||
|
||||
for (const action of allActions) {
|
||||
if (!action.effectiveShortcuts) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const shortcut of action.effectiveShortcuts) {
|
||||
if (shortcut.startsWith('global:')) {
|
||||
const translatedShortcut = shortcut.substr(7);
|
||||
|
||||
const result = globalShortcut.register(translatedShortcut, cls.wrap(async () => {
|
||||
// window may be hidden / not in focus
|
||||
mainWindow.focus();
|
||||
|
||||
mainWindow.webContents.send('globalShortcut', action.actionName);
|
||||
}));
|
||||
|
||||
if (result) {
|
||||
log.info(`Registered global shortcut ${translatedShortcut} for action ${action.actionName}`);
|
||||
}
|
||||
else {
|
||||
log.info(`Could not register global shortcut ${translatedShortcut}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.on('ready', async () => {
|
||||
app.setAppUserModelId('com.github.zadam.trilium');
|
||||
|
||||
mainWindow = await createMainWindow();
|
||||
|
||||
const result = globalShortcut.register('CommandOrControl+Alt+P', cls.wrap(async () => {
|
||||
// window may be hidden / not in focus
|
||||
mainWindow.focus();
|
||||
|
||||
mainWindow.webContents.send('create-day-sub-note');
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
log.error("Could not register global shortcut CTRL+ALT+P");
|
||||
}
|
||||
registerGlobalShortcuts();
|
||||
});
|
||||
|
||||
app.on('will-quit', () => {
|
||||
|
||||
+2
-2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -498,7 +498,7 @@
|
||||
t._started = false;
|
||||
onRenderStop();
|
||||
} else {
|
||||
setImmediate(step);
|
||||
requestIdleCallback(step, { timeout: 10 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Generated
+342
-4147
File diff suppressed because it is too large
Load Diff
+10
-18
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.37.4",
|
||||
"version": "0.38.0-beta",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@@ -29,11 +29,11 @@
|
||||
"csurf": "1.10.0",
|
||||
"dayjs": "1.8.17",
|
||||
"debug": "4.1.1",
|
||||
"ejs": "2.7.1",
|
||||
"ejs": "2.7.4",
|
||||
"electron-debug": "3.0.1",
|
||||
"electron-dl": "1.14.0",
|
||||
"electron-find": "1.0.6",
|
||||
"electron-spellchecker": "2.2.0",
|
||||
"electron-spellchecker": "2.2.1",
|
||||
"electron-window-state": "5.0.3",
|
||||
"express": "4.17.1",
|
||||
"express-session": "1.17.0",
|
||||
@@ -45,16 +45,16 @@
|
||||
"http-proxy-agent": "2.1.0",
|
||||
"https-proxy-agent": "3.0.1",
|
||||
"image-type": "4.1.0",
|
||||
"imagemin": "7.0.0",
|
||||
"imagemin": "7.0.1",
|
||||
"imagemin-giflossy": "5.1.10",
|
||||
"imagemin-mozjpeg": "8.0.0",
|
||||
"imagemin-pngquant": "8.0.0",
|
||||
"ini": "1.3.5",
|
||||
"jimp": "0.8.5",
|
||||
"mime-types": "2.1.24",
|
||||
"jimp": "0.9.3",
|
||||
"mime-types": "2.1.25",
|
||||
"moment": "2.24.0",
|
||||
"multer": "1.4.2",
|
||||
"node-abi": "2.12.0",
|
||||
"node-abi": "2.13.0",
|
||||
"open": "7.0.0",
|
||||
"pngjs": "3.4.0",
|
||||
"portscanner": "2.2.0",
|
||||
@@ -79,18 +79,10 @@
|
||||
"devDependencies": {
|
||||
"electron": "6.0.12",
|
||||
"electron-builder": "22.1.0",
|
||||
"electron-compile": "6.4.4",
|
||||
"electron-installer-debian": "2.0.1",
|
||||
"electron-packager": "14.1.0",
|
||||
"electron-rebuild": "1.8.6",
|
||||
"electron-packager": "14.1.1",
|
||||
"electron-rebuild": "1.8.8",
|
||||
"jsdoc": "3.6.3",
|
||||
"lorem-ipsum": "2.0.3",
|
||||
"xo": "0.25.3"
|
||||
},
|
||||
"xo": {
|
||||
"envs": [
|
||||
"node",
|
||||
"browser"
|
||||
]
|
||||
"lorem-ipsum": "2.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,10 @@ class Attribute extends Entity {
|
||||
|
||||
async beforeSaving() {
|
||||
if (!this.value) {
|
||||
if (this.type === 'relation') {
|
||||
throw new Error(`Cannot save relation ${this.name} since it does not target any note.`);
|
||||
}
|
||||
|
||||
// null value isn't allowed
|
||||
this.value = "";
|
||||
}
|
||||
@@ -109,6 +113,7 @@ class Attribute extends Entity {
|
||||
// cannot be static!
|
||||
updatePojo(pojo) {
|
||||
delete pojo.isOwned;
|
||||
delete pojo.__note;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+178
-33
@@ -114,6 +114,10 @@ class Note extends Entity {
|
||||
|
||||
/** @returns {Promise} */
|
||||
async setContent(content) {
|
||||
if (content === null || content === undefined) {
|
||||
throw new Error(`Cannot set null content to note ${this.noteId}`);
|
||||
}
|
||||
|
||||
// force updating note itself so that dateModified is represented correctly even for the content
|
||||
this.forcedChange = true;
|
||||
this.contentLength = content.length;
|
||||
@@ -192,26 +196,36 @@ class Note extends Entity {
|
||||
return null;
|
||||
}
|
||||
|
||||
async loadOwnedAttributesToCache() {
|
||||
this.__ownedAttributeCache = await repository.getEntities(`SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ?`, [this.noteId]);
|
||||
return this.__ownedAttributeCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Attribute[]>} attributes belonging to this specific note (excludes inherited attributes)
|
||||
* This method is a faster variant of getAttributes() which looks for only owned attributes.
|
||||
* Use when inheritance is not needed and/or in batch/performance sensitive operations.
|
||||
*
|
||||
* This method can be significantly faster than the getAttributes()
|
||||
* @param {string} [type] - (optional) attribute type to filter
|
||||
* @param {string} [name] - (optional) attribute name to filter
|
||||
* @returns {Promise<Attribute[]>} note's "owned" attributes - excluding inherited ones
|
||||
*/
|
||||
async getOwnedAttributes(type, name) {
|
||||
let query = `SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ?`;
|
||||
const params = [this.noteId];
|
||||
|
||||
if (type) {
|
||||
query += ` AND type = ?`;
|
||||
params.push(type);
|
||||
if (!this.__ownedAttributeCache) {
|
||||
await this.loadOwnedAttributesToCache();
|
||||
}
|
||||
|
||||
if (name) {
|
||||
query += ` AND name = ?`;
|
||||
params.push(name);
|
||||
if (type && name) {
|
||||
return this.__ownedAttributeCache.filter(attr => attr.type === type && attr.name === name);
|
||||
}
|
||||
else if (type) {
|
||||
return this.__ownedAttributeCache.filter(attr => attr.type === type);
|
||||
}
|
||||
else if (name) {
|
||||
return this.__ownedAttributeCache.filter(attr => attr.name === name);
|
||||
}
|
||||
else {
|
||||
return this.__ownedAttributeCache.slice();
|
||||
}
|
||||
|
||||
return await repository.getEntities(query, params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,19 +247,26 @@ class Note extends Entity {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [name] - attribute name to filter
|
||||
* @param {string} [type] - (optional) attribute type to filter
|
||||
* @param {string} [name] - (optional) attribute name to filter
|
||||
* @returns {Promise<Attribute[]>} all note's attributes, including inherited ones
|
||||
*/
|
||||
async getAttributes(name) {
|
||||
async getAttributes(type, name) {
|
||||
if (!this.__attributeCache) {
|
||||
await this.loadAttributesToCache();
|
||||
}
|
||||
|
||||
if (name) {
|
||||
if (type && name) {
|
||||
return this.__attributeCache.filter(attr => attr.type === type && attr.name === name);
|
||||
}
|
||||
else if (type) {
|
||||
return this.__attributeCache.filter(attr => attr.type === type);
|
||||
}
|
||||
else if (name) {
|
||||
return this.__attributeCache.filter(attr => attr.name === name);
|
||||
}
|
||||
else {
|
||||
return this.__attributeCache;
|
||||
return this.__attributeCache.slice();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +275,15 @@ class Note extends Entity {
|
||||
* @returns {Promise<Attribute[]>} all note's labels (attributes with type label), including inherited ones
|
||||
*/
|
||||
async getLabels(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL);
|
||||
return await this.getAttributes(LABEL, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [name] - label name to filter
|
||||
* @returns {Promise<Attribute[]>} all note's labels (attributes with type label), excluding inherited ones
|
||||
*/
|
||||
async getOwnedLabels(name) {
|
||||
return await this.getOwnedAttributes(LABEL, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,7 +291,7 @@ class Note extends Entity {
|
||||
* @returns {Promise<Attribute[]>} all note's label definitions, including inherited ones
|
||||
*/
|
||||
async getLabelDefinitions(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL_DEFINITION);
|
||||
return await this.getAttributes(LABEL_DEFINITION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,7 +299,15 @@ class Note extends Entity {
|
||||
* @returns {Promise<Attribute[]>} all note's relations (attributes with type relation), including inherited ones
|
||||
*/
|
||||
async getRelations(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION);
|
||||
return await this.getAttributes(RELATION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [name] - relation name to filter
|
||||
* @returns {Promise<Attribute[]>} all note's relations (attributes with type relation), excluding inherited ones
|
||||
*/
|
||||
async getOwnedRelations(name) {
|
||||
return await this.getOwnedAttributes(RELATION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,7 +330,7 @@ class Note extends Entity {
|
||||
* @returns {Promise<Attribute[]>} all note's relation definitions including inherited ones
|
||||
*/
|
||||
async getRelationDefinitions(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION_DEFINITION);
|
||||
return await this.getAttributes(RELATION_DEFINITION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -302,6 +339,7 @@ class Note extends Entity {
|
||||
*/
|
||||
invalidateAttributeCache() {
|
||||
this.__attributeCache = null;
|
||||
this.__ownedAttributeCache = null;
|
||||
}
|
||||
|
||||
/** @returns {Promise<void>} */
|
||||
@@ -311,11 +349,10 @@ class Note extends Entity {
|
||||
tree(noteId, level) AS (
|
||||
SELECT ?, 0
|
||||
UNION
|
||||
SELECT branches.parentNoteId, tree.level + 1 FROM branches
|
||||
SELECT branches.parentNoteId, tree.level + 1
|
||||
FROM branches
|
||||
JOIN tree ON branches.noteId = tree.noteId
|
||||
JOIN notes ON notes.noteId = branches.parentNoteId
|
||||
WHERE notes.isDeleted = 0
|
||||
AND branches.isDeleted = 0
|
||||
WHERE branches.isDeleted = 0
|
||||
),
|
||||
treeWithAttrs(noteId, level) AS (
|
||||
SELECT * FROM tree
|
||||
@@ -334,6 +371,11 @@ class Note extends Entity {
|
||||
// we order by noteId so that attributes from same note stay together. Actual noteId ordering doesn't matter.
|
||||
|
||||
const filteredAttributes = attributes.filter((attr, index) => {
|
||||
// if this exact attribute already appears then don't include it (can happen via cloning)
|
||||
if (attributes.findIndex(it => it.attributeId === attr.attributeId) !== index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (attr.isDefinition()) {
|
||||
const firstDefinitionIndex = attributes.findIndex(el => el.type === attr.type && el.name === attr.name);
|
||||
|
||||
@@ -377,6 +419,15 @@ class Note extends Entity {
|
||||
return !!await this.getAttribute(type, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type - attribute type (label, relation, etc.)
|
||||
* @param {string} name - attribute name
|
||||
* @returns {Promise<boolean>} true if note has an attribute with given type and name (excluding inherited)
|
||||
*/
|
||||
async hasOwnedAttribute(type, name) {
|
||||
return !!await this.getOwnedAttribute(type, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type - attribute type (label, relation, etc.)
|
||||
* @param {string} name - attribute name
|
||||
@@ -391,7 +442,7 @@ class Note extends Entity {
|
||||
/**
|
||||
* @param {string} type - attribute type (label, relation, etc.)
|
||||
* @param {string} name - attribute name
|
||||
* @returns {Promise<string>} attribute value of given type and name or null if no such attribute exists.
|
||||
* @returns {Promise<string|null>} attribute value of given type and name or null if no such attribute exists.
|
||||
*/
|
||||
async getAttributeValue(type, name) {
|
||||
const attr = await this.getAttribute(type, name);
|
||||
@@ -399,6 +450,17 @@ class Note extends Entity {
|
||||
return attr ? attr.value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type - attribute type (label, relation, etc.)
|
||||
* @param {string} name - attribute name
|
||||
* @returns {Promise<string|null>} attribute value of given type and name or null if no such attribute exists.
|
||||
*/
|
||||
async getOwnedAttributeValue(type, name) {
|
||||
const attr = await this.getOwnedAttribute(type, name);
|
||||
|
||||
return attr ? attr.value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on enabled, attribute is either set or removed.
|
||||
*
|
||||
@@ -426,7 +488,7 @@ class Note extends Entity {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async setAttribute(type, name, value) {
|
||||
const attributes = await this.getOwnedAttributes();
|
||||
const attributes = await this.loadOwnedAttributesToCache();
|
||||
let attr = attributes.find(attr => attr.type === type && attr.name === name);
|
||||
|
||||
if (attr) {
|
||||
@@ -460,7 +522,7 @@ class Note extends Entity {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async removeAttribute(type, name, value) {
|
||||
const attributes = await this.getOwnedAttributes();
|
||||
const attributes = await this.loadOwnedAttributesToCache();
|
||||
|
||||
for (const attribute of attributes) {
|
||||
if (attribute.type === type && (value === undefined || value === attribute.value)) {
|
||||
@@ -472,42 +534,104 @@ class Note extends Entity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise<Attribute>}
|
||||
*/
|
||||
async addAttribute(type, name, value = "") {
|
||||
const attr = new Attribute({
|
||||
noteId: this.noteId,
|
||||
type: type,
|
||||
name: name,
|
||||
value: value
|
||||
});
|
||||
|
||||
await attr.save();
|
||||
|
||||
this.invalidateAttributeCache();
|
||||
|
||||
return attr;
|
||||
}
|
||||
|
||||
async addLabel(name, value = "") {
|
||||
return await this.addAttribute(LABEL, name, value);
|
||||
}
|
||||
|
||||
async addRelation(name, targetNoteId) {
|
||||
return await this.addAttribute(RELATION, name, targetNoteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - label name
|
||||
* @returns {Promise<boolean>} true if label exists (including inherited)
|
||||
*/
|
||||
async hasLabel(name) { return await this.hasAttribute(LABEL, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - label name
|
||||
* @returns {Promise<boolean>} true if label exists (excluding inherited)
|
||||
*/
|
||||
async hasOwnedLabel(name) { return await this.hasOwnedAttribute(LABEL, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - relation name
|
||||
* @returns {Promise<boolean>} true if relation exists (including inherited)
|
||||
*/
|
||||
async hasRelation(name) { return await this.hasAttribute(RELATION, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - relation name
|
||||
* @returns {Promise<boolean>} true if relation exists (excluding inherited)
|
||||
*/
|
||||
async hasOwnedRelation(name) { return await this.hasOwnedAttribute(RELATION, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - label name
|
||||
* @returns {Promise<Attribute>} label if it exists, null otherwise
|
||||
* @returns {Promise<Attribute|null>} label if it exists, null otherwise
|
||||
*/
|
||||
async getLabel(name) { return await this.getAttribute(LABEL, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - label name
|
||||
* @returns {Promise<Attribute|null>} label if it exists, null otherwise
|
||||
*/
|
||||
async getOwnedLabel(name) { return await this.getOwnedAttribute(LABEL, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - relation name
|
||||
* @returns {Promise<Attribute>} relation if it exists, null otherwise
|
||||
* @returns {Promise<Attribute|null>} relation if it exists, null otherwise
|
||||
*/
|
||||
async getRelation(name) { return await this.getAttribute(RELATION, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - relation name
|
||||
* @returns {Promise<Attribute|null>} relation if it exists, null otherwise
|
||||
*/
|
||||
async getOwnedRelation(name) { return await this.getOwnedAttribute(RELATION, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - label name
|
||||
* @returns {Promise<string>} label value if label exists, null otherwise
|
||||
* @returns {Promise<string|null>} label value if label exists, null otherwise
|
||||
*/
|
||||
async getLabelValue(name) { return await this.getAttributeValue(LABEL, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - label name
|
||||
* @returns {Promise<string|null>} label value if label exists, null otherwise
|
||||
*/
|
||||
async getOwnedLabelValue(name) { return await this.getOwnedAttributeValue(LABEL, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - relation name
|
||||
* @returns {Promise<string>} relation value if relation exists, null otherwise
|
||||
* @returns {Promise<string|null>} relation value if relation exists, null otherwise
|
||||
*/
|
||||
async getRelationValue(name) { return await this.getAttributeValue(RELATION, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name - relation name
|
||||
* @returns {Promise<string|null>} relation value if relation exists, null otherwise
|
||||
*/
|
||||
async getOwnedRelationValue(name) { return await this.getOwnedAttributeValue(RELATION, name); }
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {Promise<Note>|null} target note of the relation or null (if target is empty or note was not found)
|
||||
@@ -518,6 +642,16 @@ class Note extends Entity {
|
||||
return relation ? await repository.getNote(relation.value) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {Promise<Note>|null} target note of the relation or null (if target is empty or note was not found)
|
||||
*/
|
||||
async getOwnedRelationTarget(name) {
|
||||
const relation = await this.getOwnedRelation(name);
|
||||
|
||||
return relation ? await repository.getNote(relation.value) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on enabled, label is either set or removed.
|
||||
*
|
||||
@@ -671,7 +805,7 @@ class Note extends Entity {
|
||||
WHERE noteId = ? AND
|
||||
isDeleted = 0 AND
|
||||
type = 'relation' AND
|
||||
name IN ('internal-link', 'image-link', 'relation-map-link')`, [this.noteId]);
|
||||
name IN ('internalLink', 'imageLink', 'relationMapLink')`, [this.noteId]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -748,6 +882,16 @@ class Note extends Entity {
|
||||
return notePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ancestorNoteId
|
||||
* @return {Promise<boolean>} - true if ancestorNoteId occurs in at least one of the note's paths
|
||||
*/
|
||||
async isDescendantOfNote(ancestorNoteId) {
|
||||
const notePaths = await this.getAllNotePaths();
|
||||
|
||||
return notePaths.some(path => path.includes(ancestorNoteId));
|
||||
}
|
||||
|
||||
beforeSaving() {
|
||||
if (!this.isDeleted) {
|
||||
this.isDeleted = false;
|
||||
@@ -788,6 +932,7 @@ class Note extends Entity {
|
||||
delete pojo.isContentAvailable;
|
||||
delete pojo.__attributeCache;
|
||||
delete pojo.content;
|
||||
/** zero references to contentHash, probably can be removed */
|
||||
delete pojo.contentHash;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import cssLoader from './services/css_loader.js';
|
||||
import dateNoteService from './services/date_notes.js';
|
||||
import sidebarService from './services/sidebar.js';
|
||||
import importService from './services/import.js';
|
||||
import keyboardActionService from "./services/keyboard_actions.js";
|
||||
|
||||
window.glob.isDesktop = utils.isDesktop;
|
||||
window.glob.isMobile = utils.isMobile;
|
||||
@@ -78,7 +79,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
|
||||
};
|
||||
|
||||
for (const appCssNoteId of window.appCssNoteIds) {
|
||||
cssLoader.requireCss(`/api/notes/download/${appCssNoteId}`);
|
||||
cssLoader.requireCss(`api/notes/download/${appCssNoteId}`);
|
||||
}
|
||||
|
||||
const wikiBaseUrl = "https://github.com/zadam/trilium/wiki/";
|
||||
@@ -108,18 +109,8 @@ $("body").on("click", "a.external", function () {
|
||||
});
|
||||
|
||||
if (utils.isElectron()) {
|
||||
require('electron').ipcRenderer.on('create-day-sub-note', async function(event) {
|
||||
const todayNote = await dateNoteService.getTodayNote();
|
||||
const notePath = await treeService.getSomeNotePath(todayNote);
|
||||
|
||||
const node = await treeService.expandToNote(notePath);
|
||||
|
||||
await noteDetailService.openEmptyTab(false);
|
||||
|
||||
await treeService.createNote(node, todayNote.noteId, 'into', {
|
||||
type: "text",
|
||||
isProtected: node.data.isProtected
|
||||
});
|
||||
require('electron').ipcRenderer.on('globalShortcut', async function(event, actionName) {
|
||||
keyboardActionService.triggerAction(actionName);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -136,7 +127,7 @@ $noteTabContainer.on("click", ".export-note-button", function () {
|
||||
$noteTabContainer.on("click", ".import-files-button",
|
||||
() => import('./dialogs/import.js').then(d => d.showDialog(treeService.getActiveNode())));
|
||||
|
||||
$noteTabContainer.on("click", ".print-note-button", async function () {
|
||||
async function printActiveNote() {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
@@ -154,7 +145,11 @@ $noteTabContainer.on("click", ".print-note-button", async function () {
|
||||
loadCSS: "libraries/codemirror/codemirror.css",
|
||||
debug: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
keyboardActionService.setGlobalActionHandler("PrintActiveNote", printActiveNote);
|
||||
|
||||
$noteTabContainer.on("click", ".print-note-button", printActiveNote);
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip({
|
||||
html: true
|
||||
|
||||
@@ -17,11 +17,21 @@ export async function showDialog(node) {
|
||||
|
||||
glob.activeDialog = $dialog;
|
||||
|
||||
$dialog.modal();
|
||||
|
||||
branchId = node.data.branchId;
|
||||
const branch = treeCache.getBranch(branchId);
|
||||
|
||||
if (branch.noteId === 'root') {
|
||||
return;
|
||||
}
|
||||
|
||||
const parentNote = await treeCache.getNote(branch.parentNoteId);
|
||||
|
||||
if (parentNote.type === 'search') {
|
||||
return;
|
||||
}
|
||||
|
||||
$dialog.modal();
|
||||
|
||||
$treePrefixInput.val(branch.prefix);
|
||||
|
||||
const noteTitle = await treeUtils.getNoteTitle(node.data.noteId);
|
||||
|
||||
@@ -22,6 +22,7 @@ export async function showDialog() {
|
||||
import('./options/other.js'),
|
||||
import('./options/sidebar.js'),
|
||||
import('./options/sync.js'),
|
||||
import('./options/keyboard_shortcuts.js'),
|
||||
]))
|
||||
.map(m => new m.default)
|
||||
.forEach(tab => {
|
||||
|
||||
@@ -3,19 +3,19 @@ import toastService from "../../services/toast.js";
|
||||
|
||||
const TPL = `
|
||||
<h4 style="margin-top: 0;">Sync</h4>
|
||||
<button id="force-full-sync-button" class="btn btn-secondary">Force full sync</button>
|
||||
<button id="force-full-sync-button" class="btn">Force full sync</button>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<button id="fill-sync-rows-button" class="btn btn-secondary">Fill sync rows</button>
|
||||
<button id="fill-sync-rows-button" class="btn">Fill sync rows</button>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h4>Debugging</h4>
|
||||
|
||||
<button id="anonymize-button" class="btn btn-secondary">Save anonymized database</button><br/><br/>
|
||||
<button id="anonymize-button" class="btn">Save anonymized database</button><br/><br/>
|
||||
|
||||
<p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata)
|
||||
for sharing online for debugging purposes without fear of leaking your personal data.</p>
|
||||
@@ -24,7 +24,7 @@ const TPL = `
|
||||
|
||||
<p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p>
|
||||
|
||||
<button id="vacuum-database-button" class="btn btn-secondary">Vacuum database</button>`;
|
||||
<button id="vacuum-database-button" class="btn">Vacuum database</button>`;
|
||||
|
||||
export default class AdvancedOptions {
|
||||
constructor() {
|
||||
|
||||
@@ -131,7 +131,7 @@ export default class ApperanceOptions {
|
||||
if (noteId) {
|
||||
// make sure the CSS is loaded
|
||||
// if the CSS has been loaded and then updated then the changes won't take effect though
|
||||
cssLoader.requireCss(`/api/notes/download/${noteId}`);
|
||||
cssLoader.requireCss(`api/notes/download/${noteId}`);
|
||||
}
|
||||
|
||||
this.$body.addClass("theme-" + newTheme);
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import server from "../../services/server.js";
|
||||
import utils from "../../services/utils.js";
|
||||
|
||||
const TPL = `
|
||||
<h4>Keyboard shortcuts</h4>
|
||||
|
||||
<p>Multiple shortcuts for the same action can be separated by comma.</p>
|
||||
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" id="keyboard-shortcut-filter" placeholder="Type text to filter shortcuts...">
|
||||
</div>
|
||||
|
||||
<div style="overflow: auto; height: 500px;">
|
||||
<table id="keyboard-shortcut-table" cellpadding="10">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Action name</th>
|
||||
<th>Shortcuts</th>
|
||||
<th>Default shortcuts</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<button class="btn btn-primary" id="options-keyboard-shortcuts-reload-app">Reload app to apply changes</button>
|
||||
|
||||
<button class="btn" id="options-keyboard-shortcuts-set-all-to-default">Set all shortcuts to the default</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let globActions;
|
||||
|
||||
export default class KeyboardShortcutsOptions {
|
||||
constructor() {
|
||||
$("#options-keyboard-shortcuts").html(TPL);
|
||||
|
||||
$("#options-keyboard-shortcuts-reload-app").on("click", () => utils.reloadApp());
|
||||
|
||||
const $table = $("#keyboard-shortcut-table tbody");
|
||||
|
||||
server.get('keyboard-actions').then(actions => {
|
||||
globActions = actions;
|
||||
|
||||
for (const action of actions) {
|
||||
const $tr = $("<tr>");
|
||||
|
||||
if (action.separator) {
|
||||
$tr.append(
|
||||
$('<td colspan="4">')
|
||||
.attr("style","background-color: var(--accented-background-color); font-weight: bold;")
|
||||
.text(action.separator)
|
||||
)
|
||||
}
|
||||
else {
|
||||
$tr.append($("<td>").text(action.actionName))
|
||||
.append($("<td>").append(
|
||||
$(`<input type="text" class="form-control">`)
|
||||
.val(action.effectiveShortcuts.join(", "))
|
||||
.attr('data-keyboard-action-name', action.actionName)
|
||||
.attr('data-default-keyboard-shortcuts', action.defaultShortcuts.join(", "))
|
||||
)
|
||||
)
|
||||
.append($("<td>").text(action.defaultShortcuts.join(", ")))
|
||||
.append($("<td>").text(action.description));
|
||||
}
|
||||
|
||||
$table.append($tr);
|
||||
}
|
||||
});
|
||||
|
||||
$table.on('change', 'input.form-control', e => {
|
||||
const $input = $(e.target);
|
||||
const actionName = $input.attr('data-keyboard-action-name');
|
||||
const shortcuts = $input.val()
|
||||
.replace('+,', "+Comma")
|
||||
.split(",")
|
||||
.map(shortcut => shortcut.replace("+Comma", "+,"))
|
||||
.filter(shortcut => !!shortcut);
|
||||
|
||||
const opts = {};
|
||||
opts['keyboardShortcuts' + actionName] = JSON.stringify(shortcuts);
|
||||
|
||||
server.put('options', opts);
|
||||
});
|
||||
|
||||
$("#options-keyboard-shortcuts-set-all-to-default").on('click', async () => {
|
||||
const confirmDialog = await import('../confirm.js');
|
||||
|
||||
if (!await confirmDialog.confirm("Do you really want to reset all keyboard shortcuts to the default?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table.find('input.form-control').each(function() {
|
||||
const defaultShortcuts = $(this).attr('data-default-keyboard-shortcuts');
|
||||
|
||||
if ($(this).val() !== defaultShortcuts) {
|
||||
$(this)
|
||||
.val(defaultShortcuts)
|
||||
.trigger('change');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const $filter = $("#keyboard-shortcut-filter");
|
||||
|
||||
$filter.on('keyup', () => {
|
||||
const filter = $filter.val().trim().toLowerCase();
|
||||
|
||||
$table.find("tr").each((i, el) => {
|
||||
if (!filter) {
|
||||
$(el).show();
|
||||
return;
|
||||
}
|
||||
|
||||
const actionName = $(el).find('input').attr('data-keyboard-action-name');
|
||||
|
||||
if (!actionName) {
|
||||
$(el).hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const action = globActions.find(act => act.actionName === actionName);
|
||||
|
||||
if (!action) {
|
||||
$(el).hide();
|
||||
return;
|
||||
}
|
||||
|
||||
$(el).toggle(!!( // !! to avoid toggle overloads with different behavior
|
||||
action.actionName.toLowerCase().includes(filter)
|
||||
|| action.defaultShortcuts.some(shortcut => shortcut.toLowerCase().includes(filter))
|
||||
|| action.effectiveShortcuts.some(shortcut => shortcut.toLowerCase().includes(filter))
|
||||
|| (action.description && action.description.toLowerCase().includes(filter))
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ const TPL = `
|
||||
|
||||
<div class="form-group">
|
||||
<label for="spell-check-language-code">Language code</label>
|
||||
<input type="email" class="form-control" id="spell-check-language-code" placeholder="for example "en-US", "de-AT"">
|
||||
<input type="text" class="form-control" id="spell-check-language-code" placeholder="for example "en-US", "de-AT"">
|
||||
</div>
|
||||
|
||||
<p>Changes to the spell check options will take effect after application restart.</p>
|
||||
@@ -132,4 +132,4 @@ export default class ProtectedSessionOptions {
|
||||
this.$imageMaxWidthHeight.val(options['imageMaxWidthHeight']);
|
||||
this.$imageJpegQuality.val(options['imageJpegQuality']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ export default class SidebarOptions {
|
||||
this.$sidebarMinWidth.val(options.sidebarMinWidth);
|
||||
this.$sidebarWidthPercent.val(options.sidebarWidthPercent);
|
||||
|
||||
if (parseInt(options.showSidebarInNewTab)) {
|
||||
if (options.showSidebarInNewTab === 'true') {
|
||||
this.$showSidebarInNewTab.attr("checked", "checked");
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -25,7 +25,7 @@ const TPL = `
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
|
||||
<button class="btn btn-secondary" type="button" data-help-page="Synchronization">Help</button>
|
||||
<button class="btn" type="button" data-help-page="Synchronization">Help</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -35,7 +35,7 @@ const TPL = `
|
||||
|
||||
<p>This will test connection and handshake to the sync server. If sync server isn't initialized, this will set it up to sync with local document.</p>
|
||||
|
||||
<button id="test-sync-button" class="btn btn-secondary">Test sync</button>`;
|
||||
<button id="test-sync-button" class="btn">Test sync</button>`;
|
||||
|
||||
export default class SyncOptions {
|
||||
constructor() {
|
||||
|
||||
@@ -154,20 +154,27 @@ class NoteShort {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [name] - attribute name to filter
|
||||
* @returns {Promise<Attribute[]>}
|
||||
* @param {string} [type] - (optional) attribute type to filter
|
||||
* @param {string} [name] - (optional) attribute name to filter
|
||||
* @returns {Promise<Attribute[]>} all note's attributes, including inherited ones
|
||||
*/
|
||||
async getAttributes(name) {
|
||||
if (!this.attributeCache) {
|
||||
this.attributeCache = (await server.get('notes/' + this.noteId + '/attributes'))
|
||||
async getAttributes(type, name) {
|
||||
if (!this.__attributeCache) {
|
||||
this.__attributeCache = (await server.get('notes/' + this.noteId + '/attributes'))
|
||||
.map(attrRow => new Attribute(this.treeCache, attrRow));
|
||||
}
|
||||
|
||||
if (name) {
|
||||
return this.attributeCache.filter(attr => attr.name === name);
|
||||
if (type && name) {
|
||||
return this.__attributeCache.filter(attr => attr.type === type && attr.name === name);
|
||||
}
|
||||
else if (type) {
|
||||
return this.__attributeCache.filter(attr => attr.type === type);
|
||||
}
|
||||
else if (name) {
|
||||
return this.__attributeCache.filter(attr => attr.name === name);
|
||||
}
|
||||
else {
|
||||
return this.attributeCache;
|
||||
return this.__attributeCache.slice();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +183,7 @@ class NoteShort {
|
||||
* @returns {Promise<Attribute[]>} all note's labels (attributes with type label), including inherited ones
|
||||
*/
|
||||
async getLabels(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL);
|
||||
return await this.getAttributes(LABEL, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,7 +191,7 @@ class NoteShort {
|
||||
* @returns {Promise<Attribute[]>} all note's label definitions, including inherited ones
|
||||
*/
|
||||
async getLabelDefinitions(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL_DEFINITION);
|
||||
return await this.getAttributes(LABEL_DEFINITION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,7 +199,7 @@ class NoteShort {
|
||||
* @returns {Promise<Attribute[]>} all note's relations (attributes with type relation), including inherited ones
|
||||
*/
|
||||
async getRelations(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION);
|
||||
return await this.getAttributes(RELATION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,7 +207,7 @@ class NoteShort {
|
||||
* @returns {Promise<Attribute[]>} all note's relation definitions including inherited ones
|
||||
*/
|
||||
async getRelationDefinitions(name) {
|
||||
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION_DEFINITION);
|
||||
return await this.getAttributes(RELATION_DEFINITION, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,8 +306,8 @@ class NoteShort {
|
||||
* Clear note's attributes cache to force fresh reload for next attribute request.
|
||||
* Cache is note instance scoped.
|
||||
*/
|
||||
invalidateAttributeCache() {
|
||||
this.attributeCache = null;
|
||||
invalidate__attributeCache() {
|
||||
this.__attributeCache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,7 +328,7 @@ class NoteShort {
|
||||
const dto = Object.assign({}, this);
|
||||
delete dto.treeCache;
|
||||
delete dto.archived;
|
||||
delete dto.attributeCache;
|
||||
delete dto.__attributeCache;
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import keyboardActionService from './keyboard_actions.js';
|
||||
const $contextMenuContainer = $("#context-menu-container");
|
||||
|
||||
let dateContextMenuOpenedMs = 0;
|
||||
@@ -69,6 +70,8 @@ async function initContextMenu(event, contextMenu) {
|
||||
|
||||
addItems($contextMenuContainer, await contextMenu.getContextMenuItems());
|
||||
|
||||
keyboardActionService.updateDisplayedShortcuts($contextMenuContainer);
|
||||
|
||||
// code below tries to detect when dropdown would overflow from page
|
||||
// in such case we'll position it above click coordinates so it will fit into client
|
||||
const clickPosition = event.pageY;
|
||||
|
||||
@@ -4,6 +4,11 @@ import zoomService from "./zoom.js";
|
||||
import protectedSessionService from "./protected_session.js";
|
||||
import searchNotesService from "./search_notes.js";
|
||||
import treeService from "./tree.js";
|
||||
import dateNoteService from "./date_notes.js";
|
||||
import noteDetailService from "./note_detail.js";
|
||||
import keyboardActionService from "./keyboard_actions.js";
|
||||
import hoistedNoteService from "./hoisted_note.js";
|
||||
import treeCache from "./tree_cache.js";
|
||||
|
||||
const NOTE_REVISIONS = "../dialogs/note_revisions.js";
|
||||
const OPTIONS = "../dialogs/options.js";
|
||||
@@ -26,89 +31,99 @@ function registerEntrypoints() {
|
||||
jQuery.hotkeys.options.filterContentEditable = false;
|
||||
jQuery.hotkeys.options.filterTextInputs = false;
|
||||
|
||||
utils.bindGlobalShortcut('ctrl+l', () => import(ADD_LINK).then(d => d.showDialog()));
|
||||
utils.bindGlobalShortcut('ctrl+shift+l', () => import(ADD_LINK).then(d => d.showDialogForClone()));
|
||||
keyboardActionService.setGlobalActionHandler("AddLinkToText", () => import(ADD_LINK).then(d => d.showDialog()));
|
||||
|
||||
$("#jump-to-note-dialog-button").on('click', () => import(JUMP_TO_NOTE).then(d => d.showDialog()));
|
||||
utils.bindGlobalShortcut('ctrl+j', () => import(JUMP_TO_NOTE).then(d => d.showDialog()));
|
||||
const showJumpToNoteDialog = () => import(JUMP_TO_NOTE).then(d => d.showDialog());
|
||||
$("#jump-to-note-dialog-button").on('click', showJumpToNoteDialog);
|
||||
keyboardActionService.setGlobalActionHandler("JumpToNote", showJumpToNoteDialog);
|
||||
|
||||
$("#recent-changes-button").on('click', () => import(RECENT_CHANGES).then(d => d.showDialog()));
|
||||
const showRecentChanges = () => import(RECENT_CHANGES).then(d => d.showDialog());
|
||||
$("#recent-changes-button").on('click', showRecentChanges);
|
||||
keyboardActionService.setGlobalActionHandler("ShowRecentChanges", showRecentChanges);
|
||||
|
||||
$("#enter-protected-session-button").on('click', protectedSessionService.enterProtectedSession);
|
||||
$("#leave-protected-session-button").on('click', protectedSessionService.leaveProtectedSession);
|
||||
|
||||
$("#toggle-search-button").on('click', searchNotesService.toggleSearch);
|
||||
utils.bindGlobalShortcut('ctrl+s', searchNotesService.toggleSearch);
|
||||
keyboardActionService.setGlobalActionHandler('SearchNotes', searchNotesService.toggleSearch);
|
||||
|
||||
const $noteTabContainer = $("#note-tab-container");
|
||||
$noteTabContainer.on("click", ".show-attributes-button", () => import(ATTRIBUTES).then(d => d.showDialog()));
|
||||
utils.bindGlobalShortcut('alt+a', () => import(ATTRIBUTES).then(d => d.showDialog()));
|
||||
|
||||
$noteTabContainer.on("click", ".show-note-info-button", () => import(NOTE_INFO).then(d => d.showDialog()));
|
||||
const showAttributesDialog = () => import(ATTRIBUTES).then(d => d.showDialog());
|
||||
$noteTabContainer.on("click", ".show-attributes-button", showAttributesDialog);
|
||||
keyboardActionService.setGlobalActionHandler("ShowAttributes", showAttributesDialog);
|
||||
|
||||
$noteTabContainer.on("click", ".show-note-revisions-button", function() {
|
||||
const showNoteInfoDialog = () => import(NOTE_INFO).then(d => d.showDialog());
|
||||
$noteTabContainer.on("click", ".show-note-info-button", showNoteInfoDialog);
|
||||
keyboardActionService.setGlobalActionHandler("ShowNoteInfo", showNoteInfoDialog);
|
||||
|
||||
const showNoteRevisionsDialog = function() {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
import(NOTE_REVISIONS).then(d => d.showCurrentNoteRevisions());
|
||||
});
|
||||
};
|
||||
|
||||
$noteTabContainer.on("click", ".show-source-button", function() {
|
||||
$noteTabContainer.on("click", ".show-note-revisions-button", showNoteRevisionsDialog);
|
||||
keyboardActionService.setGlobalActionHandler("ShowNoteRevisions", showNoteRevisionsDialog);
|
||||
|
||||
const showNoteSourceDialog = function() {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
import(NOTE_SOURCE).then(d => d.showDialog());
|
||||
});
|
||||
};
|
||||
|
||||
$noteTabContainer.on("click", ".show-link-map-button", function() {
|
||||
import(LINK_MAP).then(d => d.showDialog());
|
||||
});
|
||||
$noteTabContainer.on("click", ".show-source-button", showNoteSourceDialog);
|
||||
keyboardActionService.setGlobalActionHandler("ShowNoteSource", showNoteSourceDialog);
|
||||
|
||||
$("#options-button").on('click', () => import(OPTIONS).then(d => d.showDialog()));
|
||||
const showLinkMapDialog = () => import(LINK_MAP).then(d => d.showDialog());
|
||||
$noteTabContainer.on("click", ".show-link-map-button", showLinkMapDialog);
|
||||
keyboardActionService.setGlobalActionHandler("ShowLinkMap", showLinkMapDialog);
|
||||
|
||||
$("#show-help-button").on('click', () => import(HELP).then(d => d.showDialog()));
|
||||
utils.bindGlobalShortcut('f1', () => import(HELP).then(d => d.showDialog()));
|
||||
const showOptionsDialog = () => import(OPTIONS).then(d => d.showDialog());
|
||||
$("#options-button").on('click', showOptionsDialog);
|
||||
keyboardActionService.setGlobalActionHandler("ShowOptions", showOptionsDialog);
|
||||
|
||||
$("#open-sql-console-button").on('click', () => import(SQL_CONSOLE).then(d => d.showDialog()));
|
||||
utils.bindGlobalShortcut('alt+o', () => import(SQL_CONSOLE).then(d => d.showDialog()));
|
||||
const showHelpDialog = () => import(HELP).then(d => d.showDialog());
|
||||
$("#show-help-button").on('click', showHelpDialog);
|
||||
keyboardActionService.setGlobalActionHandler("ShowHelp", showHelpDialog);
|
||||
|
||||
const showSqlConsoleDialog = () => import(SQL_CONSOLE).then(d => d.showDialog());
|
||||
$("#open-sql-console-button").on('click', showSqlConsoleDialog);
|
||||
keyboardActionService.setGlobalActionHandler("ShowSQLConsole", showSqlConsoleDialog);
|
||||
|
||||
$("#show-about-dialog-button").on('click', () => import(ABOUT).then(d => d.showDialog()));
|
||||
|
||||
if (utils.isElectron()) {
|
||||
$("#history-navigation").show();
|
||||
$("#history-back-button").on('click', window.history.back);
|
||||
$("#history-forward-button").on('click', window.history.forward);
|
||||
keyboardActionService.setGlobalActionHandler("BackInNoteHistory", window.history.back);
|
||||
|
||||
if (utils.isMac()) {
|
||||
// Mac has a different history navigation shortcuts - https://github.com/zadam/trilium/issues/376
|
||||
utils.bindGlobalShortcut('meta+left', window.history.back);
|
||||
utils.bindGlobalShortcut('meta+right', window.history.forward);
|
||||
}
|
||||
else {
|
||||
utils.bindGlobalShortcut('alt+left', window.history.back);
|
||||
utils.bindGlobalShortcut('alt+right', window.history.forward);
|
||||
}
|
||||
$("#history-forward-button").on('click', window.history.forward);
|
||||
keyboardActionService.setGlobalActionHandler("ForwardInNoteHistory", window.history.forward);
|
||||
}
|
||||
|
||||
utils.bindGlobalShortcut('alt+m', e => {
|
||||
$(".hide-toggle").toggle();
|
||||
$("#container").toggleClass("distraction-free-mode");
|
||||
});
|
||||
// hide (toggle) everything except for the note content for zen mode
|
||||
const toggleZenMode = () => {
|
||||
$(".hide-in-zen-mode").toggle();
|
||||
$("#container").toggleClass("zen-mode");
|
||||
};
|
||||
|
||||
// hide (toggle) everything except for the note content for distraction free writing
|
||||
utils.bindGlobalShortcut('alt+t', e => {
|
||||
$("#toggle-zen-mode-button").on('click', toggleZenMode);
|
||||
keyboardActionService.setGlobalActionHandler("ToggleZenMode", toggleZenMode);
|
||||
|
||||
keyboardActionService.setGlobalActionHandler("InsertDateTimeToText", () => {
|
||||
const date = new Date();
|
||||
const dateString = utils.formatDateTime(date);
|
||||
|
||||
linkService.addTextToEditor(dateString);
|
||||
});
|
||||
|
||||
utils.bindGlobalShortcut('f5', utils.reloadApp);
|
||||
|
||||
$("#reload-frontend-button").on('click', utils.reloadApp);
|
||||
utils.bindGlobalShortcut('ctrl+r', utils.reloadApp);
|
||||
keyboardActionService.setGlobalActionHandler("ReloadFrontendApp", utils.reloadApp);
|
||||
|
||||
$("#open-dev-tools-button").toggle(utils.isElectron());
|
||||
|
||||
@@ -119,8 +134,8 @@ function registerEntrypoints() {
|
||||
return false;
|
||||
};
|
||||
|
||||
utils.bindGlobalShortcut('ctrl+shift+i', openDevTools);
|
||||
$("#open-dev-tools-button").on('click', openDevTools);
|
||||
keyboardActionService.setGlobalActionHandler("OpenDevTools", openDevTools);
|
||||
}
|
||||
|
||||
let findInPage;
|
||||
@@ -141,18 +156,16 @@ function registerEntrypoints() {
|
||||
textHoverBgColor: '#555',
|
||||
caseSelectedColor: 'var(--main-border-color)'
|
||||
});
|
||||
}
|
||||
|
||||
if (utils.isElectron()) {
|
||||
utils.bindGlobalShortcut('ctrl+f', () => {
|
||||
findInPage.openFindWindow();
|
||||
|
||||
return false;
|
||||
keyboardActionService.setGlobalActionHandler("FindInText", () => {
|
||||
if (!glob.activeDialog || !glob.activeDialog.is(":visible")) {
|
||||
findInPage.openFindWindow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const toggleFullscreen = function() {
|
||||
const toggleFullscreen = () => {
|
||||
const win = require('electron').remote.getCurrentWindow();
|
||||
|
||||
if (win.isFullScreenable()) {
|
||||
@@ -164,7 +177,7 @@ function registerEntrypoints() {
|
||||
|
||||
$("#toggle-fullscreen-button").on('click', toggleFullscreen);
|
||||
|
||||
utils.bindGlobalShortcut('f11', toggleFullscreen);
|
||||
keyboardActionService.setGlobalActionHandler("ToggleFullscreen", toggleFullscreen);
|
||||
}
|
||||
else {
|
||||
// outside of electron this is handled by the browser
|
||||
@@ -172,8 +185,8 @@ function registerEntrypoints() {
|
||||
}
|
||||
|
||||
if (utils.isElectron()) {
|
||||
utils.bindGlobalShortcut('ctrl+-', zoomService.decreaseZoomFactor);
|
||||
utils.bindGlobalShortcut('ctrl+=', zoomService.increaseZoomFactor);
|
||||
keyboardActionService.setGlobalActionHandler("ZoomOut", zoomService.decreaseZoomFactor);
|
||||
keyboardActionService.setGlobalActionHandler("ZoomIn", zoomService.increaseZoomFactor);
|
||||
}
|
||||
|
||||
$(document).on('click', "a[data-action='note-revision']", async event => {
|
||||
@@ -188,7 +201,7 @@ function registerEntrypoints() {
|
||||
return false;
|
||||
});
|
||||
|
||||
utils.bindGlobalShortcut('ctrl+shift+c', () => import(CLONE_TO).then(d => {
|
||||
keyboardActionService.setGlobalActionHandler("CloneNotesTo", () => import(CLONE_TO).then(d => {
|
||||
const activeNode = treeService.getActiveNode();
|
||||
|
||||
const selectedOrActiveNodes = treeService.getSelectedOrActiveNodes(activeNode);
|
||||
@@ -198,14 +211,57 @@ function registerEntrypoints() {
|
||||
d.showDialog(noteIds);
|
||||
}));
|
||||
|
||||
utils.bindGlobalShortcut('ctrl+shift+x', () => import(MOVE_TO).then(d => {
|
||||
keyboardActionService.setGlobalActionHandler("MoveNotesTo", () => import(MOVE_TO).then(d => {
|
||||
const activeNode = treeService.getActiveNode();
|
||||
|
||||
const selectedOrActiveNodes = treeService.getSelectedOrActiveNodes(activeNode);
|
||||
|
||||
d.showDialog(selectedOrActiveNodes);
|
||||
}));
|
||||
|
||||
keyboardActionService.setGlobalActionHandler("CreateNoteIntoDayNote", async () => {
|
||||
const todayNote = await dateNoteService.getTodayNote();
|
||||
const notePath = await treeService.getSomeNotePath(todayNote);
|
||||
|
||||
const node = await treeService.expandToNote(notePath);
|
||||
|
||||
const {note} = await treeService.createNote(node, todayNote.noteId, 'into', {
|
||||
type: "text",
|
||||
isProtected: node.data.isProtected
|
||||
});
|
||||
|
||||
await noteDetailService.openInTab(note.noteId, true);
|
||||
});
|
||||
|
||||
keyboardActionService.setGlobalActionHandler("EditBranchPrefix", async () => {
|
||||
const node = treeService.getActiveNode();
|
||||
|
||||
const editBranchPrefixDialog = await import("../dialogs/branch_prefix.js");
|
||||
editBranchPrefixDialog.showDialog(node);
|
||||
});
|
||||
|
||||
keyboardActionService.setGlobalActionHandler("ToggleNoteHoisting", async () => {
|
||||
const node = treeService.getActiveNode();
|
||||
|
||||
hoistedNoteService.getHoistedNoteId().then(async hoistedNoteId => {
|
||||
if (node.data.noteId === hoistedNoteId) {
|
||||
hoistedNoteService.unhoist();
|
||||
}
|
||||
else {
|
||||
const note = await treeCache.getNote(node.data.noteId);
|
||||
|
||||
if (note.type !== 'search') {
|
||||
hoistedNoteService.setHoistedNoteId(node.data.noteId);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
keyboardActionService.setGlobalActionHandler("SearchInSubtree", () => {
|
||||
const node = treeService.getActiveNode();
|
||||
|
||||
searchNotesService.searchInSubtree(node.data.noteId);
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import server from "./server.js";
|
||||
import utils from "./utils.js";
|
||||
|
||||
const keyboardActionRepo = {};
|
||||
|
||||
const keyboardActionsLoaded = server.get('keyboard-actions').then(actions => {
|
||||
for (const action of actions) {
|
||||
keyboardActionRepo[action.actionName] = action;
|
||||
}
|
||||
});
|
||||
|
||||
server.get('keyboard-shortcuts-for-notes').then(shortcutForNotes => {
|
||||
for (const shortcut in shortcutForNotes) {
|
||||
utils.bindGlobalShortcut(shortcut, async () => {
|
||||
const treeService = (await import("./tree.js")).default;
|
||||
|
||||
treeService.activateNote(shortcutForNotes[shortcut]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function setGlobalActionHandler(actionName, handler) {
|
||||
keyboardActionsLoaded.then(() => {
|
||||
const action = keyboardActionRepo[actionName];
|
||||
|
||||
if (!action) {
|
||||
throw new Error(`Cannot find keyboard action '${actionName}'`);
|
||||
}
|
||||
|
||||
action.handler = handler;
|
||||
|
||||
for (const shortcut of action.effectiveShortcuts) {
|
||||
if (shortcut && !shortcut.startsWith("global:")) { // global shortcuts should be handled in the electron code
|
||||
utils.bindGlobalShortcut(shortcut, handler);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setElementActionHandler($el, actionName, handler) {
|
||||
keyboardActionsLoaded.then(() => {
|
||||
const action = keyboardActionRepo[actionName];
|
||||
|
||||
if (!action) {
|
||||
throw new Error(`Cannot find keyboard action '${actionName}'`);
|
||||
}
|
||||
|
||||
// not setting action.handler since this is not global
|
||||
|
||||
for (const shortcut of action.effectiveShortcuts) {
|
||||
if (shortcut) {
|
||||
utils.bindElShortcut($el, shortcut, handler);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function triggerAction(actionName) {
|
||||
const action = await getAction(actionName);
|
||||
|
||||
if (!action.handler) {
|
||||
throw new Error(`Action ${actionName} has no handler`);
|
||||
}
|
||||
|
||||
await action.handler();
|
||||
}
|
||||
|
||||
async function getAction(actionName, silent = false) {
|
||||
await keyboardActionsLoaded;
|
||||
|
||||
const action = keyboardActionRepo[actionName];
|
||||
|
||||
if (!action) {
|
||||
if (silent) {
|
||||
console.log(`Cannot find action ${actionName}`);
|
||||
}
|
||||
else {
|
||||
throw new Error(`Cannot find action ${actionName}`);
|
||||
}
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function updateDisplayedShortcuts($container) {
|
||||
$container.find('kbd[data-kb-action]').each(async (i, el) => {
|
||||
const actionName = $(el).attr('data-kb-action');
|
||||
const action = await getAction(actionName, true);
|
||||
|
||||
if (action) {
|
||||
$(el).text(action.effectiveShortcuts.join(', '));
|
||||
}
|
||||
});
|
||||
|
||||
$container.find('button[data-kb-action],a.icon-action[data-kb-action]').each(async (i, el) => {
|
||||
const actionName = $(el).attr('data-kb-action');
|
||||
const action = await getAction(actionName, true);
|
||||
|
||||
if (action) {
|
||||
const title = $(el).attr('title');
|
||||
const shortcuts = action.effectiveShortcuts.join(', ');
|
||||
const newTitle = !title || !title.trim() ? shortcuts : `${title} (${shortcuts})`;
|
||||
|
||||
$(el).attr('title', newTitle);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(() => updateDisplayedShortcuts($(document)));
|
||||
|
||||
export default {
|
||||
setGlobalActionHandler,
|
||||
setElementActionHandler,
|
||||
triggerAction,
|
||||
getAction,
|
||||
updateDisplayedShortcuts
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
class Actions {
|
||||
constructor() {
|
||||
this.JUMP_TO = "";
|
||||
}
|
||||
}
|
||||
|
||||
const actions = new Actions();
|
||||
|
||||
function bind() {
|
||||
|
||||
}
|
||||
|
||||
export default {
|
||||
actions,
|
||||
bind
|
||||
};
|
||||
@@ -72,7 +72,7 @@ function goToLink(e) {
|
||||
|
||||
if (notePath) {
|
||||
if ((e.which === 1 && e.ctrlKey) || e.which === 2) {
|
||||
noteDetailService.openInTab(notePath);
|
||||
noteDetailService.openInTab(notePath, false);
|
||||
}
|
||||
else if (e.which === 1) {
|
||||
treeService.activateNote(notePath);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import optionsService from "./options.js";
|
||||
|
||||
const MIME_TYPES_DICT = [
|
||||
{ default: true, title: "Plain text", mime: "text/plain" },
|
||||
{ title: "APL", mime: "text/apl" },
|
||||
{ title: "PGP", mime: "application/pgp" },
|
||||
{ title: "ASN.1", mime: "text/x-ttcn-asn" },
|
||||
@@ -91,7 +92,6 @@ const MIME_TYPES_DICT = [
|
||||
{ default: true, title: "Perl", mime: "text/x-perl" },
|
||||
{ default: true, title: "PHP", mime: "text/x-php" },
|
||||
{ title: "Pig", mime: "text/x-pig" },
|
||||
{ title: "Plain Text", mime: "text/plain" },
|
||||
{ title: "PLSQL", mime: "text/x-plsql" },
|
||||
{ title: "PostgreSQL", mime: "text/x-pgsql" },
|
||||
{ title: "PowerShell", mime: "application/x-powershell" },
|
||||
@@ -168,7 +168,7 @@ function loadMimeTypes(options) {
|
||||
|| MIME_TYPES_DICT.filter(mt => mt.default).map(mt => mt.mime);
|
||||
|
||||
for (const mt of mimeTypes) {
|
||||
mt.enabled = enabledMimeTypes.includes(mt.mime);
|
||||
mt.enabled = enabledMimeTypes.includes(mt.mime) || mt.mime === 'text/plain'; // text/plain is always enabled
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import utils from "./utils.js";
|
||||
import contextMenuService from "./context_menu.js";
|
||||
import treeUtils from "./tree_utils.js";
|
||||
import tabRow from "./tab_row.js";
|
||||
import keyboardActionService from "./keyboard_actions.js";
|
||||
|
||||
const $tabContentsContainer = $("#note-tab-container");
|
||||
const $savedIndicator = $(".saved-indicator");
|
||||
@@ -34,8 +35,8 @@ async function reloadAllTabs() {
|
||||
}
|
||||
}
|
||||
|
||||
async function openInTab(notePath) {
|
||||
await loadNoteDetail(notePath, { newTab: true });
|
||||
async function openInTab(notePath, activate) {
|
||||
await loadNoteDetail(notePath, { newTab: true, activate });
|
||||
}
|
||||
|
||||
async function switchToNote(notePath) {
|
||||
@@ -273,7 +274,9 @@ async function filterTabs(noteId) {
|
||||
|
||||
async function noteDeleted(noteId) {
|
||||
for (const tc of tabContexts) {
|
||||
if (tc.notePath && tc.notePath.split("/").includes(noteId)) {
|
||||
// not removing active even if it contains deleted note since that one will move to another note (handled by deletion logic)
|
||||
// and we would lose tab context state (e.g. sidebar visibility)
|
||||
if (!tc.isActive() && tc.notePath && tc.notePath.split("/").includes(noteId)) {
|
||||
await tabRow.removeTab(tc.$tab[0]);
|
||||
}
|
||||
}
|
||||
@@ -419,33 +422,31 @@ $(tabRow.el).on('contextmenu', '.note-tab', e => {
|
||||
});
|
||||
});
|
||||
|
||||
if (utils.isElectron()) {
|
||||
utils.bindGlobalShortcut('ctrl+t', () => {
|
||||
openEmptyTab();
|
||||
});
|
||||
keyboardActionService.setGlobalActionHandler('OpenNewTab', () => {
|
||||
openEmptyTab();
|
||||
});
|
||||
|
||||
utils.bindGlobalShortcut('ctrl+w', () => {
|
||||
if (tabRow.activeTabEl) {
|
||||
tabRow.removeTab(tabRow.activeTabEl);
|
||||
}
|
||||
});
|
||||
keyboardActionService.setGlobalActionHandler('CloseActiveTab', () => {
|
||||
if (tabRow.activeTabEl) {
|
||||
tabRow.removeTab(tabRow.activeTabEl);
|
||||
}
|
||||
});
|
||||
|
||||
utils.bindGlobalShortcut('ctrl+tab', () => {
|
||||
const nextTab = tabRow.nextTabEl;
|
||||
keyboardActionService.setGlobalActionHandler('ActivateNextTab', () => {
|
||||
const nextTab = tabRow.nextTabEl;
|
||||
|
||||
if (nextTab) {
|
||||
tabRow.activateTab(nextTab);
|
||||
}
|
||||
});
|
||||
if (nextTab) {
|
||||
tabRow.activateTab(nextTab);
|
||||
}
|
||||
});
|
||||
|
||||
utils.bindGlobalShortcut('ctrl+shift+tab', () => {
|
||||
const prevTab = tabRow.previousTabEl;
|
||||
keyboardActionService.setGlobalActionHandler('ActivatePreviousTab', () => {
|
||||
const prevTab = tabRow.previousTabEl;
|
||||
|
||||
if (prevTab) {
|
||||
tabRow.activateTab(prevTab);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (prevTab) {
|
||||
tabRow.activateTab(prevTab);
|
||||
}
|
||||
});
|
||||
|
||||
tabRow.addListener('activeTabChange', openTabsChanged);
|
||||
tabRow.addListener('tabRemove', openTabsChanged);
|
||||
|
||||
@@ -3,7 +3,7 @@ import bundleService from "./bundle.js";
|
||||
import toastService from "./toast.js";
|
||||
import server from "./server.js";
|
||||
import noteDetailService from "./note_detail.js";
|
||||
import utils from "./utils.js";
|
||||
import keyboardActionService from "./keyboard_actions.js";
|
||||
|
||||
class NoteDetailCode {
|
||||
|
||||
@@ -17,7 +17,7 @@ class NoteDetailCode {
|
||||
this.$editorEl = this.$component.find('.note-detail-code-editor');
|
||||
this.$executeScriptButton = ctx.$tabContent.find(".execute-script-button");
|
||||
|
||||
utils.bindElShortcut(ctx.$tabContent, "ctrl+return", () => this.executeCurrentNote());
|
||||
keyboardActionService.setElementActionHandler(ctx.$tabContent, 'RunActiveNote', () => this.executeCurrentNote());
|
||||
|
||||
this.$executeScriptButton.on('click', () => this.executeCurrentNote());
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ class NoteDetailRelationMap {
|
||||
const noteId = this.idToNoteId($noteBox.prop("id"));
|
||||
|
||||
if (cmd === "open-in-new-tab") {
|
||||
noteDetailService.openInTab(noteId);
|
||||
noteDetailService.openInTab(noteId, false);
|
||||
}
|
||||
else if (cmd === "remove") {
|
||||
const confirmDialog = await import('../dialogs/confirm.js');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import libraryLoader from "./library_loader.js";
|
||||
import treeService from './tree.js';
|
||||
import noteAutocompleteService from './note_autocomplete.js';
|
||||
import mimeTypesService from './mime_types.js';
|
||||
|
||||
class NoteDetailText {
|
||||
/**
|
||||
@@ -41,6 +42,16 @@ class NoteDetailText {
|
||||
// textEditor might have been initialized during previous await so checking again
|
||||
// looks like double initialization can freeze CKEditor pretty badly
|
||||
if (!this.textEditor) {
|
||||
const codeBlockLanguages =
|
||||
(await mimeTypesService.getMimeTypes())
|
||||
.filter(mt => mt.enabled)
|
||||
.map(mt => {
|
||||
return {
|
||||
language: mt.mime.toLowerCase().replace(/[\W_]+/g,"-"),
|
||||
label: mt.title
|
||||
}
|
||||
});
|
||||
|
||||
this.textEditor = await BalloonEditor.create(this.$editorEl[0], {
|
||||
placeholder: "Type the content of your note here ...",
|
||||
mention: {
|
||||
@@ -77,6 +88,9 @@ class NoteDetailText {
|
||||
minimumCharacters: 0
|
||||
}
|
||||
]
|
||||
},
|
||||
codeBlock: {
|
||||
languages: codeBlockLanguages
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ class Options {
|
||||
return this.arr[key];
|
||||
}
|
||||
|
||||
getNames() {
|
||||
return Object.keys(this.arr);
|
||||
}
|
||||
|
||||
getJson(key) {
|
||||
try {
|
||||
return JSON.parse(this.arr[key]);
|
||||
|
||||
@@ -246,11 +246,15 @@ class TabContext {
|
||||
}
|
||||
|
||||
setCurrentNotePathToHash() {
|
||||
if (this.$tab[0] === this.tabRow.activeTabEl) {
|
||||
if (this.isActive()) {
|
||||
document.location.hash = (this.notePath || "") + "-" + this.tabId;
|
||||
}
|
||||
}
|
||||
|
||||
isActive() {
|
||||
return this.$tab[0] === this.tabRow.activeTabEl;
|
||||
}
|
||||
|
||||
setupClasses() {
|
||||
for (const clazz of Array.from(this.$tab[0].classList)) { // create copy to safely iterate over while removing classes
|
||||
if (clazz !== 'note-tab') {
|
||||
|
||||
@@ -29,6 +29,7 @@ const tabTemplate = `
|
||||
</div>`;
|
||||
|
||||
const newTabButtonTemplate = `<div class="note-new-tab" title="Add new tab">+</div>`;
|
||||
const fillerTemplate = `<div class="tab-row-filler"></div>`;
|
||||
|
||||
class TabRow {
|
||||
constructor(el) {
|
||||
@@ -40,9 +41,10 @@ class TabRow {
|
||||
|
||||
this.setupStyleEl();
|
||||
this.setupEvents();
|
||||
this.layoutTabs();
|
||||
this.setupDraggabilly();
|
||||
this.setupNewButton();
|
||||
this.setupFiller();
|
||||
this.layoutTabs();
|
||||
this.setVisibility();
|
||||
}
|
||||
|
||||
@@ -109,12 +111,17 @@ class TabRow {
|
||||
|
||||
const widths = [];
|
||||
let extraWidthRemaining = totalExtraWidthDueToFlooring;
|
||||
|
||||
for (let i = 0; i < numberOfTabs; i += 1) {
|
||||
const extraWidth = flooredClampedTargetWidth < TAB_CONTENT_MAX_WIDTH && extraWidthRemaining > 0 ? 1 : 0;
|
||||
widths.push(flooredClampedTargetWidth + extraWidth);
|
||||
if (extraWidthRemaining > 0) extraWidthRemaining -= 1;
|
||||
}
|
||||
|
||||
if (this.fillerEl) {
|
||||
this.fillerEl.style.width = extraWidthRemaining + "px";
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
@@ -129,8 +136,9 @@ class TabRow {
|
||||
});
|
||||
|
||||
const newTabPosition = position;
|
||||
const fillerPosition = position + 32;
|
||||
|
||||
return {tabPositions, newTabPosition};
|
||||
return {tabPositions, newTabPosition, fillerPosition};
|
||||
}
|
||||
|
||||
layoutTabs() {
|
||||
@@ -151,13 +159,14 @@ class TabRow {
|
||||
|
||||
let styleHTML = '';
|
||||
|
||||
const {tabPositions, newTabPosition} = this.getTabPositions();
|
||||
const {tabPositions, newTabPosition, fillerPosition} = this.getTabPositions();
|
||||
|
||||
tabPositions.forEach((position, i) => {
|
||||
styleHTML += `.note-tab:nth-child(${ i + 1 }) { transform: translate3d(${ position }px, 0, 0)} `;
|
||||
});
|
||||
|
||||
styleHTML += `.note-new-tab { transform: translate3d(${ newTabPosition }px, 0, 0) } `;
|
||||
styleHTML += `.tab-row-filler { transform: translate3d(${ fillerPosition }px, 0, 0) } `;
|
||||
|
||||
this.styleEl.innerHTML = styleHTML;
|
||||
}
|
||||
@@ -387,11 +396,18 @@ class TabRow {
|
||||
this.newTabEl = div.firstElementChild;
|
||||
|
||||
this.tabContentEl.appendChild(this.newTabEl);
|
||||
this.layoutTabs();
|
||||
|
||||
this.newTabEl.addEventListener('click', _ => this.emit('newTab'));
|
||||
}
|
||||
|
||||
setupFiller() {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = fillerTemplate;
|
||||
this.fillerEl = div.firstElementChild;
|
||||
|
||||
this.tabContentEl.appendChild(this.fillerEl);
|
||||
}
|
||||
|
||||
closest(value, array) {
|
||||
let closest = Infinity;
|
||||
let closestIndex = -1;
|
||||
|
||||
@@ -9,13 +9,12 @@ import server from './server.js';
|
||||
import treeCache from './tree_cache.js';
|
||||
import toastService from "./toast.js";
|
||||
import treeBuilder from "./tree_builder.js";
|
||||
import treeKeyBindings from "./tree_keybindings.js";
|
||||
import Branch from '../entities/branch.js';
|
||||
import NoteShort from '../entities/note_short.js';
|
||||
import treeKeyBindingService from "./tree_keybindings.js";
|
||||
import hoistedNoteService from '../services/hoisted_note.js';
|
||||
import optionsService from "../services/options.js";
|
||||
import TreeContextMenu from "./tree_context_menu.js";
|
||||
import bundle from "./bundle.js";
|
||||
import keyboardActionService from "./keyboard_actions.js";
|
||||
|
||||
const $tree = $("#tree");
|
||||
const $createTopLevelNoteButton = $("#create-top-level-note-button");
|
||||
@@ -368,7 +367,7 @@ async function treeInitialized() {
|
||||
const notePath = location.hash.substr(1);
|
||||
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
||||
|
||||
if (await treeCache.noteExists(noteId)) {
|
||||
if (noteId && await treeCache.noteExists(noteId)) {
|
||||
for (const tab of openTabs) {
|
||||
tab.active = false;
|
||||
}
|
||||
@@ -430,7 +429,7 @@ async function treeInitialized() {
|
||||
setFrontendAsLoaded();
|
||||
}
|
||||
|
||||
function initFancyTree(tree) {
|
||||
async function initFancyTree(tree) {
|
||||
utils.assertArguments(tree);
|
||||
|
||||
$tree.fancytree({
|
||||
@@ -473,7 +472,7 @@ function initFancyTree(tree) {
|
||||
collapse: (event, data) => setExpandedToServer(data.node.data.branchId, false),
|
||||
init: (event, data) => treeInitialized(), // don't collapse to short form
|
||||
hotkeys: {
|
||||
keydown: treeKeyBindings
|
||||
keydown: await treeKeyBindingService.getKeyboardBindings()
|
||||
},
|
||||
dnd5: dragAndDropSetup,
|
||||
lazyLoad: function(event, data) {
|
||||
@@ -627,6 +626,8 @@ async function createNewTopLevelNote() {
|
||||
async function createNote(node, parentNoteId, target, extraOptions = {}) {
|
||||
utils.assertArguments(node, parentNoteId, target);
|
||||
|
||||
extraOptions.activate = extraOptions.activate === undefined ? true : !!extraOptions.activate;
|
||||
|
||||
// if isProtected isn't available (user didn't enter password yet), then note is created as unencrypted
|
||||
// but this is quite weird since user doesn't see WHERE the note is being created so it shouldn't occur often
|
||||
if (!extraOptions.isProtected || !protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||
@@ -649,11 +650,9 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) {
|
||||
|
||||
const newNoteName = extraOptions.title || "new note";
|
||||
|
||||
const {note, branch} = await server.post('notes/' + parentNoteId + '/children', {
|
||||
const {note, branch} = await server.post(`notes/${parentNoteId}/children?target=${target}&targetBranchId=${node.data.branchId}`, {
|
||||
title: newNoteName,
|
||||
content: extraOptions.content,
|
||||
target: target,
|
||||
target_branchId: node.data.branchId,
|
||||
content: extraOptions.content || "",
|
||||
isProtected: extraOptions.isProtected,
|
||||
type: extraOptions.type
|
||||
});
|
||||
@@ -670,7 +669,7 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) {
|
||||
const noteEntity = await treeCache.getNote(note.noteId);
|
||||
const branchEntity = treeCache.getBranch(branch.branchId);
|
||||
|
||||
let newNode = {
|
||||
let newNodeData = {
|
||||
title: newNoteName,
|
||||
noteId: branchEntity.noteId,
|
||||
parentNoteId: parentNoteId,
|
||||
@@ -685,8 +684,11 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) {
|
||||
key: utils.randomString(12) // this should prevent some "duplicate key" errors
|
||||
};
|
||||
|
||||
/** @var {FancytreeNode} */
|
||||
let newNode;
|
||||
|
||||
if (target === 'after') {
|
||||
await node.appendSibling(newNode).setActive(true);
|
||||
newNode = node.appendSibling(newNodeData);
|
||||
}
|
||||
else if (target === 'into') {
|
||||
if (!node.getChildren() && node.isFolder()) {
|
||||
@@ -696,10 +698,10 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) {
|
||||
await node.setExpanded();
|
||||
}
|
||||
else {
|
||||
node.addChildren(newNode);
|
||||
node.addChildren(newNodeData);
|
||||
}
|
||||
|
||||
await node.getLastChild().setActive(true);
|
||||
newNode = node.getLastChild();
|
||||
|
||||
const parentNoteEntity = await treeCache.getNote(node.data.noteId);
|
||||
|
||||
@@ -711,14 +713,18 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) {
|
||||
toastService.throwError("Unrecognized target: " + target);
|
||||
}
|
||||
|
||||
if (extraOptions.activate) {
|
||||
await newNode.setActive(true);
|
||||
}
|
||||
|
||||
clearSelectedNodes(); // to unmark previously active node
|
||||
|
||||
// need to refresh because original doesn't have methods like .getParent()
|
||||
newNode = getNodesByNoteId(branchEntity.noteId)[0];
|
||||
newNodeData = getNodesByNoteId(branchEntity.noteId)[0];
|
||||
|
||||
// following for cycle will make sure that also clones of a parent are refreshed
|
||||
for (const newParentNode of getNodesByNoteId(parentNoteId)) {
|
||||
if (newParentNode.key === newNode.getParent().key) {
|
||||
if (newParentNode.key === newNodeData.getParent().key) {
|
||||
// we've added a note into this one so no need to refresh
|
||||
continue;
|
||||
}
|
||||
@@ -756,7 +762,7 @@ async function sortAlphabetically(noteId) {
|
||||
async function showTree() {
|
||||
const tree = await loadTree();
|
||||
|
||||
initFancyTree(tree);
|
||||
await initFancyTree(tree);
|
||||
}
|
||||
|
||||
ws.subscribeToMessages(message => {
|
||||
@@ -789,7 +795,7 @@ ws.subscribeToOutsideSyncMessages(async syncData => {
|
||||
syncData.filter(sync => sync.entityName === 'attributes').forEach(sync => {
|
||||
const note = treeCache.notes[sync.noteId];
|
||||
|
||||
if (note && note.attributeCache) {
|
||||
if (note && note.__attributeCache) {
|
||||
noteIdsToRefresh.add(sync.entityId);
|
||||
}
|
||||
});
|
||||
@@ -799,7 +805,7 @@ ws.subscribeToOutsideSyncMessages(async syncData => {
|
||||
}
|
||||
});
|
||||
|
||||
utils.bindGlobalShortcut('ctrl+o', async () => {
|
||||
keyboardActionService.setGlobalActionHandler('CreateNoteAfter', async () => {
|
||||
const node = getActiveNode();
|
||||
const parentNoteId = node.data.parentNoteId;
|
||||
const isProtected = await treeUtils.getParentProtectedStatus(node);
|
||||
@@ -863,7 +869,7 @@ async function reloadNotes(noteIds, activateNotePath = null) {
|
||||
if (activateNotePath) {
|
||||
const node = await getNodeFromPath(activateNotePath);
|
||||
|
||||
if (node) {
|
||||
if (node && !node.isActive()) {
|
||||
await node.setActive(true);
|
||||
}
|
||||
}
|
||||
@@ -871,9 +877,9 @@ async function reloadNotes(noteIds, activateNotePath = null) {
|
||||
|
||||
window.glob.createNoteInto = createNoteInto;
|
||||
|
||||
utils.bindGlobalShortcut('ctrl+p', createNoteInto);
|
||||
keyboardActionService.setGlobalActionHandler('CreateNoteInto', createNoteInto);
|
||||
|
||||
utils.bindGlobalShortcut('ctrl+.', scrollToActiveNote);
|
||||
keyboardActionService.setGlobalActionHandler('ScrollToActiveNote', scrollToActiveNote);
|
||||
|
||||
$(window).bind('hashchange', async function() {
|
||||
if (isNotePathInAddress()) {
|
||||
@@ -890,7 +896,7 @@ $tree.on('mousedown', '.fancytree-title', e => {
|
||||
|
||||
treeUtils.getNotePath(node).then(notePath => {
|
||||
if (notePath) {
|
||||
noteDetailService.openInTab(notePath);
|
||||
noteDetailService.openInTab(notePath, false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -911,7 +917,7 @@ async function duplicateNote(noteId, parentNoteId) {
|
||||
}
|
||||
|
||||
|
||||
utils.bindGlobalShortcut('alt+c', () => collapseTree()); // don't use shortened form since collapseTree() accepts argument
|
||||
keyboardActionService.setGlobalActionHandler('CollapseTree', () => collapseTree()); // don't use shortened form since collapseTree() accepts argument
|
||||
$collapseTreeButton.on('click', () => collapseTree());
|
||||
|
||||
$createTopLevelNoteButton.on('click', createNewTopLevelNote);
|
||||
|
||||
@@ -42,53 +42,54 @@ class TreeContextMenu {
|
||||
|| (selNodes.length === 1 && selNodes[0] === this.node);
|
||||
|
||||
const notSearch = note.type !== 'search';
|
||||
const parentNotSearch = parentNote.type !== 'search';
|
||||
const parentNotSearch = !parentNote || parentNote.type !== 'search';
|
||||
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
|
||||
|
||||
return [
|
||||
{ title: "Open in new tab", cmd: "openInTab", uiIcon: "empty", enabled: noSelectedNotes },
|
||||
{ title: "Insert note after <kbd>Ctrl+O</kbd>", cmd: "insertNoteAfter", uiIcon: "plus",
|
||||
{ title: 'Open in new tab', cmd: "openInTab", uiIcon: "empty", enabled: noSelectedNotes },
|
||||
{ title: 'Insert note after <kbd data-kb-action="CreateNoteAfter"></kbd>', cmd: "insertNoteAfter", uiIcon: "plus",
|
||||
items: insertNoteAfterEnabled ? this.getNoteTypeItems("insertNoteAfter") : null,
|
||||
enabled: insertNoteAfterEnabled && noSelectedNotes },
|
||||
{ title: "Insert child note <kbd>Ctrl+P</kbd>", cmd: "insertChildNote", uiIcon: "plus",
|
||||
{ title: 'Insert child note <kbd data-kb-action="CreateNoteInto"></kbd>', cmd: "insertChildNote", uiIcon: "plus",
|
||||
items: notSearch ? this.getNoteTypeItems("insertChildNote") : null,
|
||||
enabled: notSearch && noSelectedNotes },
|
||||
{ title: "Delete <kbd>Delete</kbd>", cmd: "delete", uiIcon: "trash",
|
||||
{ title: 'Delete <kbd data-kb-action="DeleteNotes"></kbd>', cmd: "delete", uiIcon: "trash",
|
||||
enabled: isNotRoot && !isHoisted && parentNotSearch },
|
||||
{ title: "----" },
|
||||
{ title: "Search in subtree <kbd>Ctrl+Shift+S</kbd>", cmd: "searchInSubtree", uiIcon: "search",
|
||||
{ title: 'Search in subtree <kbd data-kb-action="SearchInSubtree"></kbd>', cmd: "searchInSubtree", uiIcon: "search",
|
||||
enabled: notSearch && noSelectedNotes },
|
||||
isHoisted ? null : { title: "Hoist note <kbd>Ctrl-H</kbd>", cmd: "hoist", uiIcon: "empty", enabled: noSelectedNotes && notSearch },
|
||||
!isHoisted || !isNotRoot ? null : { title: "Unhoist note <kbd>Ctrl-H</kbd>", cmd: "unhoist", uiIcon: "arrow-up" },
|
||||
{ title: "Edit branch prefix <kbd>F2</kbd>", cmd: "editBranchPrefix", uiIcon: "empty",
|
||||
isHoisted ? null : { title: 'Hoist note <kbd data-kb-action="ToggleNoteHoisting"></kbd>', cmd: "hoist", uiIcon: "empty", enabled: noSelectedNotes && notSearch },
|
||||
!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: "----" },
|
||||
{ title: "Protect subtree", cmd: "protectSubtree", uiIcon: "check-shield", enabled: noSelectedNotes },
|
||||
{ title: "Unprotect subtree", cmd: "unprotectSubtree", uiIcon: "shield", enabled: noSelectedNotes },
|
||||
{ title: "----" },
|
||||
{ title: "Copy / clone <kbd>Ctrl+C</kbd>", cmd: "copy", uiIcon: "copy",
|
||||
{ title: 'Copy / clone <kbd data-kb-action="CopyNotesToClipboard"></kbd>', cmd: "copy", uiIcon: "copy",
|
||||
enabled: isNotRoot && !isHoisted },
|
||||
{ title: "Clone to ... <kbd>Ctrl+Shift+C</kbd>", cmd: "cloneTo", uiIcon: "empty",
|
||||
{ title: 'Clone to ... <kbd data-kb-action="CloneNotesTo"></kbd>', cmd: "cloneTo", uiIcon: "empty",
|
||||
enabled: isNotRoot && !isHoisted },
|
||||
{ title: "Cut <kbd>Ctrl+X</kbd>", cmd: "cut", uiIcon: "cut",
|
||||
{ title: 'Cut <kbd data-kb-action="CutNotesToClipboard"></kbd>', cmd: "cut", uiIcon: "cut",
|
||||
enabled: isNotRoot && !isHoisted && parentNotSearch },
|
||||
{ title: "Move to ... <kbd>Ctrl+Shift+X</kbd>", cmd: "moveTo", uiIcon: "empty",
|
||||
{ title: 'Move to ... <kbd data-kb-action="MoveNotesTo"></kbd>', cmd: "moveTo", uiIcon: "empty",
|
||||
enabled: isNotRoot && !isHoisted && parentNotSearch },
|
||||
{ title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "paste",
|
||||
{ title: 'Paste into <kbd data-kb-action="PasteNotesFromClipboard"></kbd>', cmd: "pasteInto", uiIcon: "paste",
|
||||
enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes },
|
||||
{ title: "Paste after", cmd: "pasteAfter", uiIcon: "paste",
|
||||
{ title: 'Paste after', cmd: "pasteAfter", uiIcon: "paste",
|
||||
enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes },
|
||||
{ title: "Duplicate note here", cmd: "duplicateNote", uiIcon: "empty",
|
||||
enabled: noSelectedNotes && parentNotSearch && (!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) },
|
||||
enabled: noSelectedNotes && parentNotSearch && isNotRoot && !isHoisted && (!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) },
|
||||
{ title: "----" },
|
||||
{ title: "Export", cmd: "export", uiIcon: "empty",
|
||||
enabled: notSearch && noSelectedNotes },
|
||||
{ title: "Import into note", cmd: "importIntoNote", uiIcon: "empty",
|
||||
enabled: notSearch && noSelectedNotes },
|
||||
{ title: "----" },
|
||||
{ title: "Collapse subtree <kbd>Alt+-</kbd>", cmd: "collapseSubtree", uiIcon: "align-justify", enabled: noSelectedNotes },
|
||||
{ title: "Force note sync", cmd: "forceNoteSync", uiIcon: "recycle", enabled: noSelectedNotes },
|
||||
{ title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: "empty", enabled: noSelectedNotes && notSearch }
|
||||
{ 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 }
|
||||
] },
|
||||
].filter(row => row !== null);
|
||||
}
|
||||
|
||||
@@ -96,7 +97,7 @@ class TreeContextMenu {
|
||||
if (cmd === 'openInTab') {
|
||||
const notePath = await treeUtils.getNotePath(this.node);
|
||||
|
||||
noteDetailService.openInTab(notePath);
|
||||
noteDetailService.openInTab(notePath, false);
|
||||
}
|
||||
else if (cmd.startsWith("insertNoteAfter")) {
|
||||
const parentNoteId = this.node.data.parentNoteId;
|
||||
|
||||
@@ -3,150 +3,10 @@ import treeChangesService from "./branches.js";
|
||||
import treeService from "./tree.js";
|
||||
import hoistedNoteService from "./hoisted_note.js";
|
||||
import clipboard from "./clipboard.js";
|
||||
import treeCache from "./tree_cache.js";
|
||||
import searchNoteService from "./search_notes.js";
|
||||
import utils from "./utils.js";
|
||||
import keyboardActionService from "./keyboard_actions.js";
|
||||
|
||||
const keyBindings = {
|
||||
"del": node => {
|
||||
treeChangesService.deleteNodes(treeService.getSelectedOrActiveNodes(node));
|
||||
},
|
||||
"ctrl+up": node => {
|
||||
const beforeNode = node.getPrevSibling();
|
||||
|
||||
if (beforeNode !== null) {
|
||||
treeChangesService.moveBeforeNode([node], beforeNode);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+down": node => {
|
||||
const afterNode = node.getNextSibling();
|
||||
if (afterNode !== null) {
|
||||
treeChangesService.moveAfterNode([node], afterNode);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+left": node => {
|
||||
treeChangesService.moveNodeUpInHierarchy(node);
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+right": node => {
|
||||
const toNode = node.getPrevSibling();
|
||||
|
||||
if (toNode !== null) {
|
||||
treeChangesService.moveToNode([node], toNode);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"shift+up": () => {
|
||||
const node = treeService.getFocusedNode();
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.isActive()) {
|
||||
node.setSelected(true);
|
||||
}
|
||||
|
||||
node.navigate($.ui.keyCode.UP, false).then(() => {
|
||||
const currentNode = treeService.getFocusedNode();
|
||||
|
||||
if (currentNode.isSelected()) {
|
||||
node.setSelected(false);
|
||||
}
|
||||
|
||||
currentNode.setSelected(true);
|
||||
});
|
||||
|
||||
return false;
|
||||
},
|
||||
"shift+down": () => {
|
||||
const node = treeService.getFocusedNode();
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.isActive()) {
|
||||
node.setSelected(true);
|
||||
}
|
||||
|
||||
node.navigate($.ui.keyCode.DOWN, false).then(() => {
|
||||
const currentNode = treeService.getFocusedNode();
|
||||
|
||||
if (currentNode.isSelected()) {
|
||||
node.setSelected(false);
|
||||
}
|
||||
|
||||
currentNode.setSelected(true);
|
||||
});
|
||||
|
||||
return false;
|
||||
},
|
||||
"f2": async node => {
|
||||
const editBranchPrefixDialog = await import("../dialogs/branch_prefix.js");
|
||||
editBranchPrefixDialog.showDialog(node);
|
||||
},
|
||||
"alt+-": node => {
|
||||
treeService.collapseTree(node);
|
||||
},
|
||||
"alt+s": node => {
|
||||
treeService.sortAlphabetically(node.data.noteId);
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+a": node => {
|
||||
for (const child of node.getParent().getChildren()) {
|
||||
child.setSelected(true);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+c": node => {
|
||||
clipboard.copy(treeService.getSelectedOrActiveNodes(node));
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+x": node => {
|
||||
clipboard.cut(treeService.getSelectedOrActiveNodes(node));
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+v": node => {
|
||||
clipboard.pasteInto(node);
|
||||
|
||||
return false;
|
||||
},
|
||||
"return": node => {
|
||||
noteDetailService.focusOnTitle();
|
||||
|
||||
return false;
|
||||
},
|
||||
"backspace": async node => {
|
||||
if (!await hoistedNoteService.isRootNode(node)) {
|
||||
node.getParent().setActive().then(treeService.clearSelectedNodes);
|
||||
}
|
||||
},
|
||||
"ctrl+h": node => {
|
||||
hoistedNoteService.getHoistedNoteId().then(async hoistedNoteId => {
|
||||
if (node.data.noteId === hoistedNoteId) {
|
||||
hoistedNoteService.unhoist();
|
||||
}
|
||||
else {
|
||||
const note = await treeCache.getNote(node.data.noteId);
|
||||
|
||||
if (note.type !== 'search') {
|
||||
hoistedNoteService.setHoistedNoteId(node.data.noteId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
},
|
||||
const fixedKeyBindings = {
|
||||
// code below shouldn't be necessary normally, however there's some problem with interaction with context menu plugin
|
||||
// after opening context menu, standard shortcuts don't work, but they are detected here
|
||||
// so we essentially takeover the standard handling with our implementation.
|
||||
@@ -168,13 +28,153 @@ const keyBindings = {
|
||||
"down": node => {
|
||||
node.navigate($.ui.keyCode.DOWN, true).then(treeService.clearSelectedNodes);
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+shift+s": node => {
|
||||
searchNoteService.searchInSubtree(node.data.noteId);
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default keyBindings;
|
||||
const templates = {
|
||||
"DeleteNotes": node => {
|
||||
treeChangesService.deleteNodes(treeService.getSelectedOrActiveNodes(node));
|
||||
},
|
||||
"MoveNoteUp": node => {
|
||||
const beforeNode = node.getPrevSibling();
|
||||
|
||||
if (beforeNode !== null) {
|
||||
treeChangesService.moveBeforeNode([node], beforeNode);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"MoveNoteDown": node => {
|
||||
const afterNode = node.getNextSibling();
|
||||
if (afterNode !== null) {
|
||||
treeChangesService.moveAfterNode([node], afterNode);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"MoveNoteUpInHierarchy": node => {
|
||||
treeChangesService.moveNodeUpInHierarchy(node);
|
||||
|
||||
return false;
|
||||
},
|
||||
"MoveNoteDownInHierarchy": node => {
|
||||
const toNode = node.getPrevSibling();
|
||||
|
||||
if (toNode !== null) {
|
||||
treeChangesService.moveToNode([node], toNode);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"AddNoteAboveToSelection": () => {
|
||||
const node = treeService.getFocusedNode();
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.isActive()) {
|
||||
node.setSelected(true);
|
||||
}
|
||||
|
||||
const prevSibling = node.getPrevSibling();
|
||||
|
||||
if (prevSibling) {
|
||||
prevSibling.setActive(true, {noEvents: true});
|
||||
|
||||
if (prevSibling.isSelected()) {
|
||||
node.setSelected(false);
|
||||
}
|
||||
|
||||
prevSibling.setSelected(true);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"AddNoteBelowToSelection": () => {
|
||||
const node = treeService.getFocusedNode();
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.isActive()) {
|
||||
node.setSelected(true);
|
||||
}
|
||||
|
||||
const nextSibling = node.getNextSibling();
|
||||
|
||||
if (nextSibling) {
|
||||
nextSibling.setActive(true, {noEvents: true});
|
||||
|
||||
if (nextSibling.isSelected()) {
|
||||
node.setSelected(false);
|
||||
}
|
||||
|
||||
nextSibling.setSelected(true);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"CollapseSubtree": node => {
|
||||
treeService.collapseTree(node);
|
||||
},
|
||||
"SortChildNotes": node => {
|
||||
treeService.sortAlphabetically(node.data.noteId);
|
||||
|
||||
return false;
|
||||
},
|
||||
"SelectAllNotesInParent": node => {
|
||||
for (const child of node.getParent().getChildren()) {
|
||||
child.setSelected(true);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"CopyNotesToClipboard": node => {
|
||||
clipboard.copy(treeService.getSelectedOrActiveNodes(node));
|
||||
|
||||
return false;
|
||||
},
|
||||
"CutNotesToClipboard": node => {
|
||||
clipboard.cut(treeService.getSelectedOrActiveNodes(node));
|
||||
|
||||
return false;
|
||||
},
|
||||
"PasteNotesFromClipboard": node => {
|
||||
clipboard.pasteInto(node);
|
||||
|
||||
return false;
|
||||
},
|
||||
"EditNoteTitle": node => {
|
||||
noteDetailService.focusOnTitle();
|
||||
|
||||
return false;
|
||||
},
|
||||
"ActivateParentNote": async node => {
|
||||
if (!await hoistedNoteService.isRootNode(node)) {
|
||||
node.getParent().setActive().then(treeService.clearSelectedNodes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async function getKeyboardBindings() {
|
||||
const bindings = Object.assign({}, fixedKeyBindings);
|
||||
|
||||
for (const actionName in templates) {
|
||||
const action = await keyboardActionService.getAction(actionName);
|
||||
|
||||
for (const shortcut of action.effectiveShortcuts || []) {
|
||||
const normalizedShortcut = utils.normalizeShortcut(shortcut);
|
||||
|
||||
bindings[normalizedShortcut] = templates[actionName];
|
||||
}
|
||||
}
|
||||
|
||||
return bindings;
|
||||
}
|
||||
|
||||
export default {
|
||||
getKeyboardBindings
|
||||
};
|
||||
@@ -137,10 +137,7 @@ function bindGlobalShortcut(keyboardShortcut, handler) {
|
||||
|
||||
function bindElShortcut($el, keyboardShortcut, handler) {
|
||||
if (isDesktop()) {
|
||||
if (isMac()) {
|
||||
// use CMD (meta) instead of CTRL for all shortcuts
|
||||
keyboardShortcut = keyboardShortcut.replace("ctrl", "meta");
|
||||
}
|
||||
keyboardShortcut = normalizeShortcut(keyboardShortcut);
|
||||
|
||||
$el.bind('keydown', keyboardShortcut, e => {
|
||||
handler(e);
|
||||
@@ -151,6 +148,18 @@ function bindElShortcut($el, keyboardShortcut, handler) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize to the form expected by the jquery.hotkeys.js
|
||||
*/
|
||||
function normalizeShortcut(shortcut) {
|
||||
return shortcut
|
||||
.toLowerCase()
|
||||
.replace("enter", "return")
|
||||
.replace("delete", "del")
|
||||
.replace("ctrl+alt", "alt+ctrl")
|
||||
.replace("meta+alt", "alt+meta"); // alt needs to be first;
|
||||
}
|
||||
|
||||
function isMobile() {
|
||||
return window.device === "mobile"
|
||||
// window.device is not available in setup
|
||||
@@ -259,5 +268,6 @@ export default {
|
||||
closeActiveDialog,
|
||||
isHtmlEmpty,
|
||||
clearBrowserCache,
|
||||
getUrlForDownload
|
||||
getUrlForDownload,
|
||||
normalizeShortcut
|
||||
};
|
||||
@@ -8,7 +8,8 @@ const outsideSyncMessageHandlers = [];
|
||||
const messageHandlers = [];
|
||||
|
||||
let ws;
|
||||
let lastSyncId = window.glob.maxSyncIdAtLoad;
|
||||
let lastAcceptedSyncId = window.glob.maxSyncIdAtLoad;
|
||||
let lastProcessedSyncId = window.glob.maxSyncIdAtLoad;
|
||||
let lastPingTs;
|
||||
let syncDataQueue = [];
|
||||
|
||||
@@ -84,7 +85,7 @@ async function handleMessage(event) {
|
||||
let syncIdReachedListeners = [];
|
||||
|
||||
function waitForSyncId(desiredSyncId) {
|
||||
if (desiredSyncId <= lastSyncId) {
|
||||
if (desiredSyncId <= lastProcessedSyncId) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -99,14 +100,14 @@ function waitForSyncId(desiredSyncId) {
|
||||
|
||||
function checkSyncIdListeners() {
|
||||
syncIdReachedListeners
|
||||
.filter(l => l.desiredSyncId <= lastSyncId)
|
||||
.filter(l => l.desiredSyncId <= lastProcessedSyncId)
|
||||
.forEach(l => l.resolvePromise());
|
||||
|
||||
syncIdReachedListeners = syncIdReachedListeners
|
||||
.filter(l => l.desiredSyncId > lastSyncId);
|
||||
.filter(l => l.desiredSyncId > lastProcessedSyncId);
|
||||
|
||||
syncIdReachedListeners.filter(l => Date.now() > l.start - 60000)
|
||||
.forEach(l => console.log(`Waiting for syncId ${l.desiredSyncId} while current is ${lastSyncId} for ${Math.floor((Date.now() - l.start) / 1000)}s`));
|
||||
.forEach(l => console.log(`Waiting for syncId ${l.desiredSyncId} while current is ${lastProcessedSyncId} for ${Math.floor((Date.now() - l.start) / 1000)}s`));
|
||||
}
|
||||
|
||||
async function consumeSyncData() {
|
||||
@@ -116,51 +117,60 @@ async function consumeSyncData() {
|
||||
|
||||
const outsideSyncData = allSyncData.filter(sync => sync.sourceId !== glob.sourceId);
|
||||
|
||||
// we set lastAcceptedSyncId even before sync processing and send ping so that backend can start sending more updates
|
||||
lastAcceptedSyncId = Math.max(lastAcceptedSyncId, allSyncData[allSyncData.length - 1].id);
|
||||
sendPing();
|
||||
|
||||
// the update process should be synchronous as a whole but individual handlers can run in parallel
|
||||
await Promise.all([
|
||||
...allSyncMessageHandlers.map(syncHandler => syncHandler(allSyncData)),
|
||||
...outsideSyncMessageHandlers.map(syncHandler => syncHandler(outsideSyncData))
|
||||
]);
|
||||
|
||||
lastSyncId = allSyncData[allSyncData.length - 1].id;
|
||||
lastProcessedSyncId = Math.max(lastProcessedSyncId, allSyncData[allSyncData.length - 1].id);
|
||||
}
|
||||
}
|
||||
|
||||
function connectWebSocket() {
|
||||
const protocol = document.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const loc = window.location;
|
||||
const webSocketUri = (loc.protocol === "https:" ? "wss:" : "ws:")
|
||||
+ "//" + loc.host + loc.pathname;
|
||||
|
||||
// use wss for secure messaging
|
||||
const ws = new WebSocket(protocol + "://" + location.host);
|
||||
ws.onopen = () => console.debug(utils.now(), "Connected to server with WebSocket");
|
||||
const ws = new WebSocket(webSocketUri);
|
||||
ws.onopen = () => console.debug(utils.now(), `Connected to server ${webSocketUri} with WebSocket`);
|
||||
ws.onmessage = handleMessage;
|
||||
// we're not handling ws.onclose here because reconnection is done in sendPing()
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
async function sendPing() {
|
||||
if (Date.now() - lastPingTs > 30000) {
|
||||
console.log(utils.now(), "Lost connection to server");
|
||||
}
|
||||
|
||||
if (ws.readyState === ws.OPEN) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'ping',
|
||||
lastSyncId: lastAcceptedSyncId
|
||||
}));
|
||||
}
|
||||
else if (ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING) {
|
||||
console.log(utils.now(), "WS closed or closing, trying to reconnect");
|
||||
|
||||
ws = connectWebSocket();
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
ws = connectWebSocket();
|
||||
|
||||
lastSyncId = glob.maxSyncIdAtLoad;
|
||||
lastAcceptedSyncId = glob.maxSyncIdAtLoad;
|
||||
lastProcessedSyncId = glob.maxSyncIdAtLoad;
|
||||
lastPingTs = Date.now();
|
||||
|
||||
setInterval(async () => {
|
||||
if (Date.now() - lastPingTs > 30000) {
|
||||
console.log(utils.now(), "Lost connection to server");
|
||||
}
|
||||
|
||||
if (ws.readyState === ws.OPEN) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'ping',
|
||||
lastSyncId: lastSyncId
|
||||
}));
|
||||
}
|
||||
else if (ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING) {
|
||||
console.log(utils.now(), "WS closed or closing, trying to reconnect");
|
||||
|
||||
ws = connectWebSocket();
|
||||
}
|
||||
}, 1000);
|
||||
setInterval(sendPing, 1000);
|
||||
}, 0);
|
||||
|
||||
subscribeToMessages(message => {
|
||||
|
||||
@@ -18,7 +18,7 @@ class SimilarNotesWidget extends StandardWidget {
|
||||
// remember which title was when we found the similar notes
|
||||
this.title = this.ctx.note.title;
|
||||
|
||||
const similarNotes = await server.get('similar_notes/' + this.ctx.note.noteId);
|
||||
const similarNotes = await server.get('similar-notes/' + this.ctx.note.noteId);
|
||||
|
||||
if (similarNotes.length === 0) {
|
||||
this.$body.text("No similar notes found ...");
|
||||
|
||||
@@ -18,7 +18,7 @@ body {
|
||||
grid-gap: 0;
|
||||
}
|
||||
|
||||
#container.distraction-free-mode {
|
||||
#container.zen-mode {
|
||||
grid-template-areas:
|
||||
"tab-container" !important;
|
||||
grid-template-rows: auto
|
||||
@@ -59,7 +59,7 @@ body {
|
||||
background-color: var(--header-background-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
#header button {
|
||||
@@ -68,18 +68,26 @@ body {
|
||||
margin-bottom: 2px;
|
||||
margin-top: 2px;
|
||||
margin-right: 8px;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
#header button.btn-sm .bx {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
#header button:hover {
|
||||
border-color: var(--button-border-color) !important;
|
||||
}
|
||||
|
||||
#history-navigation {
|
||||
margin: 0 15px 0 5px;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
#global-buttons {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 10px 0 10px 0;
|
||||
padding: 3px 0 3px 0;
|
||||
border: 1px solid var(--main-border-color);
|
||||
border-radius: 7px;
|
||||
margin: 5px 15px 5px 5px;
|
||||
@@ -89,6 +97,11 @@ body {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
#context-menu-container {
|
||||
max-height: 100vh;
|
||||
overflow: auto; /* make it scrollable when exceeding total height of the window */
|
||||
}
|
||||
|
||||
#context-menu-container, #context-menu-container .dropdown-menu {
|
||||
padding: 3px 0 0;
|
||||
z-index: 1111;
|
||||
@@ -111,6 +124,7 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
|
||||
top: 0;
|
||||
left: 100%;
|
||||
margin-top: -6px;
|
||||
min-width: 15rem;
|
||||
}
|
||||
|
||||
/* rotate caret on hover */
|
||||
@@ -145,7 +159,7 @@ body {
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--main-border-color);
|
||||
border: 1px solid var(--scrollbar-border-color);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
@@ -165,6 +179,13 @@ body {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
border: 1px solid transparent;
|
||||
padding: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.refresh-search-button:hover {
|
||||
border-color: var(--button-border-color);
|
||||
}
|
||||
|
||||
.note-title-row {
|
||||
@@ -215,7 +236,7 @@ body {
|
||||
.note-new-tab {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 32px;
|
||||
height: 33px;
|
||||
width: 32px;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
@@ -223,6 +244,7 @@ body {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--button-border-color);
|
||||
}
|
||||
|
||||
.note-new-tab:hover {
|
||||
@@ -230,6 +252,14 @@ body {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.tab-row-filler {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, var(--button-border-color), transparent);
|
||||
height: 1px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.note-tab-row .note-tab[active] {
|
||||
z-index: 5;
|
||||
}
|
||||
@@ -244,6 +274,7 @@ body {
|
||||
top: 10px;
|
||||
animation: note-tab-was-just-added 120ms forwards ease-in-out;
|
||||
}
|
||||
|
||||
.note-tab-row .note-tab .note-tab-wrapper {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
@@ -256,11 +287,14 @@ body {
|
||||
border-top-right-radius: 8px;
|
||||
overflow: hidden;
|
||||
pointer-events: all;
|
||||
background-image: linear-gradient(to bottom, var(--accented-background-color), var(--main-background-color));
|
||||
background-color: var(--accented-background-color);
|
||||
border-bottom: 1px solid var(--button-border-color);
|
||||
}
|
||||
|
||||
.note-tab-row .note-tab[active] .note-tab-wrapper {
|
||||
background-image: linear-gradient(to bottom, var(--more-accented-background-color), var(--main-background-color));
|
||||
background-color: var(--main-background-color);
|
||||
border: 1px solid var(--button-border-color);
|
||||
border-bottom: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -268,6 +302,7 @@ body {
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.note-tab-row .note-tab .note-tab-title {
|
||||
flex: 1;
|
||||
vertical-align: top;
|
||||
@@ -275,12 +310,15 @@ body {
|
||||
white-space: nowrap;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.note-tab-row .note-tab[is-small] .note-tab-title {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.note-tab-row .note-tab[active] .note-tab-title {
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.note-tab-row .note-tab .note-tab-drag-handle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -290,6 +328,7 @@ body {
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
}
|
||||
|
||||
.note-tab-row .note-tab .note-tab-close {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
@@ -348,6 +387,14 @@ body {
|
||||
|
||||
.hide-sidebar-button {
|
||||
color: var(--main-text-color);
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
padding: 2px 8px 2px 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.hide-sidebar-button:hover {
|
||||
border-color: var(--button-border-color);
|
||||
}
|
||||
|
||||
.note-detail-sidebar {
|
||||
@@ -380,6 +427,8 @@ body {
|
||||
border: 0;
|
||||
background: inherit;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted-text-color) !important;
|
||||
}
|
||||
|
||||
.note-detail-sidebar .widget-header-action {
|
||||
@@ -388,7 +437,9 @@ body {
|
||||
}
|
||||
|
||||
.note-detail-sidebar .widget-help {
|
||||
color: var(--main-text-color);
|
||||
color: var(--muted-text-color);
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.note-detail-sidebar .widget-help.no-link:hover {
|
||||
|
||||
@@ -19,7 +19,7 @@ html, body {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: space-around;
|
||||
padding: 10px 0 10px 0;
|
||||
padding: 3px 0 3px 0;
|
||||
margin: 0 10px 0 16px;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,12 @@ ul.fancytree-container {
|
||||
content: "\e9f8";
|
||||
}
|
||||
|
||||
/** some common text styling for cssClass label */
|
||||
span.fancytree-node.underline .fancytree-title { text-decoration: underline; }
|
||||
span.fancytree-node.dotted .fancytree-title { text-decoration: dotted; }
|
||||
span.fancytree-node.bold .fancytree-title { font-weight: bold; }
|
||||
span.fancytree-node.muted { opacity: 0.6; }
|
||||
|
||||
.note-title[readonly] {
|
||||
background: inherit;
|
||||
}
|
||||
@@ -144,9 +150,9 @@ ul.fancytree-container {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/** we disable shield background when in distraction free mode because I couldn't get it to stay static
|
||||
/** we disable shield background when in zen mode because I couldn't get it to stay static
|
||||
(it kept growing with content) */
|
||||
#container:not(.distraction-free-mode) .note-tab-content.protected {
|
||||
#container:not(.zen-mode) .note-tab-content.protected {
|
||||
/* DON'T COLLAPSE THE RULES INTO SINGLE ONE, BACKGROUND WON'T DISPLAY */
|
||||
background: url('../images/shield.svg') no-repeat;
|
||||
background-size: contain;
|
||||
@@ -227,9 +233,13 @@ span.fancytree-node.archived {
|
||||
|
||||
.icon-action:hover {
|
||||
text-decoration: none;
|
||||
border-color: var(--button-border-color);
|
||||
}
|
||||
|
||||
.icon-action {
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
@@ -411,11 +421,25 @@ div.ui-tooltip {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
#sql-console-query .CodeMirror-scroll {
|
||||
min-height: inherit !important;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border-radius: var(--button-border-radius);
|
||||
}
|
||||
|
||||
.btn:not(.btn-primary):not(.btn-secondary):not(.btn-danger) {
|
||||
.btn.btn-primary {
|
||||
border-color: var(--primary-button-border-color);
|
||||
background-color: var(--primary-button-background-color);
|
||||
color: var(--primary-button-text-color);
|
||||
}
|
||||
|
||||
.btn.btn-primary kbd {
|
||||
color: var(--primary-button-text-color);
|
||||
}
|
||||
|
||||
.btn:not(.btn-primary) {
|
||||
border-color: var(--button-border-color);
|
||||
background-color: var(--button-background-color);
|
||||
color: var(--button-text-color);
|
||||
@@ -423,6 +447,7 @@ div.ui-tooltip {
|
||||
|
||||
.btn.active:not(.btn-primary) {
|
||||
background-color: var(--button-disabled-background-color) !important;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.note-path-list a.current {
|
||||
@@ -460,9 +485,10 @@ button.icon-button {
|
||||
margin: auto;
|
||||
/* setting the display to block since "table" doesn't support scrolling */
|
||||
display: block;
|
||||
flex-basis: content;
|
||||
flex-shrink: 1;
|
||||
/** flex-basis: content; - use once "content" is implemented by chrome */
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
max-height: 30%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,26 +10,30 @@
|
||||
--main-text-color: black;
|
||||
--main-border-color: #ccc;
|
||||
--accented-background-color: #f5f5f5;
|
||||
--more-accented-background-color: #ccc;
|
||||
--header-background-color: #f8f8f8;
|
||||
--button-background-color: #eee;
|
||||
--button-disabled-background-color: #ccc;
|
||||
--more-accented-background-color: #ddd;
|
||||
--header-background-color: #fff;
|
||||
--button-background-color: #fff;
|
||||
--button-disabled-background-color: #ddd;
|
||||
--button-border-color: #ddd;
|
||||
--button-text-color: black;
|
||||
--button-border-radius: 5px;
|
||||
--muted-text-color: #444;
|
||||
--primary-button-background-color: #6c757d;
|
||||
--primary-button-text-color: white;
|
||||
--primary-button-border-color: #6c757d;
|
||||
--muted-text-color: #666;
|
||||
--input-text-color: black;
|
||||
--input-background-color: white;
|
||||
--hover-item-text-color: black;
|
||||
--hover-item-background-color: #eee;
|
||||
--active-item-text-color: black;
|
||||
--active-item-background-color: #ccc;
|
||||
--active-item-background-color: #ddd;
|
||||
--menu-text-color: black;
|
||||
--menu-background-color: white;
|
||||
--tooltip-background-color: #f8f8f8;
|
||||
--link-color: blue;
|
||||
--modal-background-color: white;
|
||||
--modal-backdrop-color: black;
|
||||
--scrollbar-border-color: #ddd;
|
||||
}
|
||||
|
||||
body.theme-black {
|
||||
@@ -43,6 +47,9 @@ body.theme-black {
|
||||
--button-border-color: #444;
|
||||
--button-text-color: white;
|
||||
--button-border-radius: 5px;
|
||||
--primary-button-background-color: #888;
|
||||
--primary-button-text-color: white;
|
||||
--primary-button-border-color: #999;
|
||||
--muted-text-color: #ccc;
|
||||
--input-text-color: white;
|
||||
--input-background-color: black;
|
||||
@@ -56,6 +63,7 @@ body.theme-black {
|
||||
--link-color: lightskyblue;
|
||||
--modal-background-color: black;
|
||||
--modal-backdrop-color: #444;
|
||||
--scrollbar-border-color: #888;
|
||||
}
|
||||
|
||||
body.theme-black .CodeMirror {
|
||||
@@ -65,7 +73,7 @@ body.theme-black .CodeMirror {
|
||||
body.theme-dark {
|
||||
--main-background-color: #333;
|
||||
--main-text-color: white;
|
||||
--main-border-color: #ddd;
|
||||
--main-border-color: #aaa;
|
||||
--accented-background-color: #555;
|
||||
--more-accented-background-color: #777;
|
||||
--header-background-color: #333;
|
||||
@@ -73,6 +81,9 @@ body.theme-dark {
|
||||
--button-border-color: #444;
|
||||
--button-text-color: white;
|
||||
--button-border-radius: 5px;
|
||||
--primary-button-background-color: #888;
|
||||
--primary-button-text-color: white;
|
||||
--primary-button-border-color: #999;
|
||||
--muted-text-color: #ccc;
|
||||
--input-text-color: white;
|
||||
--input-background-color: #333;
|
||||
@@ -86,6 +97,7 @@ body.theme-dark {
|
||||
--link-color: lightskyblue;
|
||||
--modal-background-color: #333;
|
||||
--modal-backdrop-color: #444;
|
||||
--scrollbar-border-color: #888;
|
||||
}
|
||||
|
||||
body.theme-dark .CodeMirror {
|
||||
@@ -184,7 +196,7 @@ body {
|
||||
|
||||
--ck-color-todo-list-checkmark-border: var(--main-border-color);
|
||||
|
||||
--ck-color-engine-placeholder-text: var(--main-text-color);
|
||||
--ck-color-engine-placeholder-text: var(--muted-text-color);
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
@@ -15,7 +15,7 @@ async function findClippingNote(todayNote, pageUrl) {
|
||||
const notes = await todayNote.getDescendantNotesWithLabel('pageUrl', pageUrl);
|
||||
|
||||
for (const note of notes) {
|
||||
if (await note.getLabelValue('clipType') === 'clippings') {
|
||||
if (await note.getOwnedLabelValue('clipType') === 'clippings') {
|
||||
return note;
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,11 @@ async function addClipping(req) {
|
||||
let clippingNote = await findClippingNote(todayNote, pageUrl);
|
||||
|
||||
if (!clippingNote) {
|
||||
clippingNote = (await noteService.createNote(todayNote.noteId, title, '')).note;
|
||||
clippingNote = (await noteService.createNewNote({
|
||||
parentNoteId: todayNote.noteId,
|
||||
title: title,
|
||||
content: ''
|
||||
})).note;
|
||||
|
||||
await clippingNote.setLabel('clipType', 'clippings');
|
||||
await clippingNote.setLabel('pageUrl', pageUrl);
|
||||
@@ -51,7 +55,11 @@ async function createNote(req) {
|
||||
|
||||
const todayNote = await dateNoteService.getDateNote(dateUtils.localNowDate());
|
||||
|
||||
const {note} = await noteService.createNote(todayNote.noteId, title, content);
|
||||
const {note} = await noteService.createNewNote({
|
||||
parentNoteId: todayNote.noteId,
|
||||
title,
|
||||
content
|
||||
});
|
||||
|
||||
await note.setLabel('clipType', clipType);
|
||||
|
||||
@@ -88,7 +96,7 @@ async function addImagesToNote(images, note, content) {
|
||||
noteId: note.noteId,
|
||||
type: 'relation',
|
||||
value: imageNote.noteId,
|
||||
name: 'image-link'
|
||||
name: 'imageLink'
|
||||
}).save();
|
||||
|
||||
console.log(`Replacing ${imageId} with ${url}`);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
|
||||
const keyboardActions = require('../../services/keyboard_actions');
|
||||
const sql = require('../../services/sql');
|
||||
|
||||
async function getKeyboardActions() {
|
||||
return await keyboardActions.getKeyboardActions();
|
||||
}
|
||||
|
||||
async function getShortcutsForNotes() {
|
||||
return await sql.getMap(`
|
||||
SELECT value, noteId
|
||||
FROM attributes
|
||||
WHERE isDeleted = 0
|
||||
AND type = 'label'
|
||||
AND name = 'keyboardShortcut'`);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getKeyboardActions,
|
||||
getShortcutsForNotes
|
||||
};
|
||||
@@ -112,15 +112,21 @@ async function eraseNoteRevision(req) {
|
||||
}
|
||||
|
||||
async function getEditedNotesOnDate(req) {
|
||||
const date = req.params.date;
|
||||
const date = utils.sanitizeSql(req.params.date);
|
||||
|
||||
const notes = await repository.getEntities(`
|
||||
select distinct notes.*
|
||||
from notes
|
||||
left join note_revisions using (noteId)
|
||||
where substr(notes.dateCreated, 0, 11) = ?
|
||||
or substr(notes.dateModified, 0, 11) = ?
|
||||
or substr(note_revisions.dateLastEdited, 0, 11) = ?`, [date, date, date]);
|
||||
SELECT notes.*
|
||||
FROM notes
|
||||
WHERE noteId IN (
|
||||
SELECT noteId FROM notes
|
||||
WHERE notes.dateCreated LIKE '${date}%'
|
||||
OR notes.dateModified LIKE '${date}%'
|
||||
UNION ALL
|
||||
SELECT noteId FROM note_revisions
|
||||
WHERE note_revisions.dateLastEdited LIKE '${date}%'
|
||||
)
|
||||
ORDER BY isDeleted
|
||||
LIMIT 50`);
|
||||
|
||||
for (const note of notes) {
|
||||
const notePath = noteCacheService.getNotePath(note.noteId);
|
||||
|
||||
+8
-30
@@ -27,36 +27,13 @@ async function getNote(req) {
|
||||
return note;
|
||||
}
|
||||
|
||||
async function getChildren(req) {
|
||||
const parentNoteId = req.params.parentNoteId;
|
||||
const parentNote = await repository.getNote(parentNoteId);
|
||||
|
||||
if (!parentNote) {
|
||||
return [404, `Note ${parentNoteId} has not been found.`];
|
||||
}
|
||||
|
||||
const ret = [];
|
||||
|
||||
for (const childNote of await parentNote.getChildNotes()) {
|
||||
ret.push({
|
||||
noteId: childNote.noteId,
|
||||
title: childNote.title,
|
||||
relations: (await childNote.getRelations()).map(relation => { return {
|
||||
attributeId: relation.attributeId,
|
||||
name: relation.name,
|
||||
targetNoteId: relation.value
|
||||
}; })
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
async function createNote(req) {
|
||||
const parentNoteId = req.params.parentNoteId;
|
||||
const newNote = req.body;
|
||||
const params = Object.assign({}, req.body); // clone
|
||||
params.parentNoteId = req.params.parentNoteId;
|
||||
|
||||
const { note, branch } = await noteService.createNewNote(parentNoteId, newNote, req);
|
||||
const { target, targetBranchId } = req.query;
|
||||
|
||||
const { note, branch } = await noteService.createNewNoteWithTarget(target, targetBranchId, params);
|
||||
|
||||
await treeService.setCssClassesToNotes([note]);
|
||||
|
||||
@@ -128,7 +105,9 @@ async function getRelationMap(req) {
|
||||
noteTitles: {},
|
||||
relations: [],
|
||||
// relation name => inverse relation name
|
||||
inverseRelations: {}
|
||||
inverseRelations: {
|
||||
'internalLink': 'internalLink'
|
||||
}
|
||||
};
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
@@ -194,7 +173,6 @@ module.exports = {
|
||||
sortNotes,
|
||||
protectSubtree,
|
||||
setNoteTypeMime,
|
||||
getChildren,
|
||||
getRelationMap,
|
||||
changeTitle,
|
||||
duplicateNote
|
||||
|
||||
@@ -5,7 +5,7 @@ const log = require('../../services/log');
|
||||
const attributes = require('../../services/attributes');
|
||||
|
||||
// options allowed to be updated directly in options dialog
|
||||
const ALLOWED_OPTIONS = [
|
||||
const ALLOWED_OPTIONS = new Set([
|
||||
'protectedSessionTimeout',
|
||||
'noteRevisionSnapshotTimeInterval',
|
||||
'zoomFactor',
|
||||
@@ -37,23 +37,32 @@ const ALLOWED_OPTIONS = [
|
||||
'spellCheckLanguageCode',
|
||||
'imageMaxWidthHeight',
|
||||
'imageJpegQuality'
|
||||
];
|
||||
]);
|
||||
|
||||
async function getOptions() {
|
||||
return await optionService.getOptionsMap(ALLOWED_OPTIONS);
|
||||
const optionMap = await optionService.getOptionsMap();
|
||||
const resultMap = {};
|
||||
|
||||
for (const optionName in optionMap) {
|
||||
if (isAllowed(optionName)) {
|
||||
resultMap[optionName] = optionMap[optionName];
|
||||
}
|
||||
}
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
async function updateOption(req) {
|
||||
const {name, value} = req.params;
|
||||
|
||||
if (!update(name, value)) {
|
||||
if (!await update(name, value)) {
|
||||
return [400, "not allowed option to change"];
|
||||
}
|
||||
}
|
||||
|
||||
async function updateOptions(req) {
|
||||
for (const optionName in req.body) {
|
||||
if (!update(optionName, req.body[optionName])) {
|
||||
if (!await update(optionName, req.body[optionName])) {
|
||||
// this should be improved
|
||||
// it should return 400 instead of current 500, but at least it now rollbacks transaction
|
||||
throw new Error(`${optionName} is not allowed to change`);
|
||||
@@ -62,7 +71,7 @@ async function updateOptions(req) {
|
||||
}
|
||||
|
||||
async function update(name, value) {
|
||||
if (!ALLOWED_OPTIONS.includes(name)) {
|
||||
if (!isAllowed(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -81,7 +90,7 @@ async function getUserThemes() {
|
||||
const ret = [];
|
||||
|
||||
for (const note of notes) {
|
||||
let value = await note.getLabelValue('appTheme');
|
||||
let value = await note.getOwnedLabelValue('appTheme');
|
||||
|
||||
if (!value) {
|
||||
value = note.title.toLowerCase().replace(/[^a-z0-9]/gi, '-');
|
||||
@@ -97,6 +106,10 @@ async function getUserThemes() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
function isAllowed(name) {
|
||||
return ALLOWED_OPTIONS.has(name) || name.startsWith("keyboardShortcuts");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getOptions,
|
||||
updateOption,
|
||||
|
||||
@@ -26,10 +26,10 @@ async function uploadImage(req) {
|
||||
async function saveNote(req) {
|
||||
const parentNote = await dateNoteService.getDateNote(req.headers['x-local-date']);
|
||||
|
||||
const {note, branch} = await noteService.createNewNote(parentNote.noteId, {
|
||||
const {note, branch} = await noteService.createNewNote({
|
||||
parentNoteId: parentNote.noteId,
|
||||
title: req.body.title,
|
||||
content: req.body.content,
|
||||
target: 'into',
|
||||
isProtected: false,
|
||||
type: 'text',
|
||||
mime: 'text/html'
|
||||
|
||||
@@ -34,6 +34,7 @@ const dateNotesRoute = require('./api/date_notes');
|
||||
const linkMapRoute = require('./api/link_map');
|
||||
const clipperRoute = require('./api/clipper');
|
||||
const similarNotesRoute = require('./api/similar_notes');
|
||||
const keysRoute = require('./api/keys');
|
||||
|
||||
const log = require('../services/log');
|
||||
const express = require('express');
|
||||
@@ -127,7 +128,6 @@ function register(app) {
|
||||
apiRoute(PUT, '/api/notes/:noteId', notesApiRoute.updateNote);
|
||||
apiRoute(DELETE, '/api/notes/:noteId', notesApiRoute.deleteNote);
|
||||
apiRoute(POST, '/api/notes/:parentNoteId/children', notesApiRoute.createNote);
|
||||
apiRoute(GET, '/api/notes/:parentNoteId/children', notesApiRoute.getChildren);
|
||||
apiRoute(PUT, '/api/notes/:noteId/sort', notesApiRoute.sortNotes);
|
||||
apiRoute(PUT, '/api/notes/:noteId/protect/:isProtected', notesApiRoute.protectSubtree);
|
||||
apiRoute(PUT, /\/api\/notes\/(.*)\/type\/(.*)\/mime\/(.*)/, notesApiRoute.setNoteTypeMime);
|
||||
@@ -242,7 +242,10 @@ function register(app) {
|
||||
route(POST, '/api/clipper/notes', clipperMiddleware, clipperRoute.createNote, apiResultHandler);
|
||||
route(POST, '/api/clipper/open/:noteId', clipperMiddleware, clipperRoute.openNote, apiResultHandler);
|
||||
|
||||
apiRoute(GET, '/api/similar_notes/:noteId', similarNotesRoute.getSimilarNotes);
|
||||
apiRoute(GET, '/api/similar-notes/:noteId', similarNotesRoute.getSimilarNotes);
|
||||
|
||||
apiRoute(GET, '/api/keyboard-actions', keysRoute.getKeyboardActions);
|
||||
apiRoute(GET, '/api/keyboard-shortcuts-for-notes', keysRoute.getShortcutsForNotes);
|
||||
|
||||
app.use('', router);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ async function anonymize() {
|
||||
|
||||
await db.run("UPDATE notes SET title = 'title'");
|
||||
await db.run("UPDATE note_contents SET content = 'text'");
|
||||
await db.run("UPDATE note_revisions SET title = 'title', content = 'text'");
|
||||
await db.run("UPDATE note_revisions SET title = 'title'");
|
||||
await db.run("UPDATE note_revision_contents SET content = 'title'");
|
||||
await db.run("UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label'");
|
||||
await db.run("UPDATE attributes SET name = 'name' WHERE type = 'relation'");
|
||||
await db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL");
|
||||
await db.run(`UPDATE options SET value = 'anonymized' WHERE name IN
|
||||
('documentSecret', 'encryptedDataKey', 'passwordVerificationHash',
|
||||
|
||||
@@ -4,8 +4,8 @@ const build = require('./build');
|
||||
const packageJson = require('../../package');
|
||||
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
||||
|
||||
const APP_DB_VERSION = 153;
|
||||
const SYNC_VERSION = 11;
|
||||
const APP_DB_VERSION = 155;
|
||||
const SYNC_VERSION = 12;
|
||||
const CLIPPER_PROTOCOL_VERSION = "1.0";
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -21,6 +21,7 @@ const BUILTIN_ATTRIBUTES = [
|
||||
{ type: 'label', name: 'readOnly' },
|
||||
{ type: 'label', name: 'cssClass' },
|
||||
{ type: 'label', name: 'iconClass' },
|
||||
{ type: 'label', name: 'keyboardShortcut' },
|
||||
{ type: 'label', name: 'run', isDangerous: true },
|
||||
{ type: 'label', name: 'customRequestHandler', isDangerous: true },
|
||||
{ type: 'label', name: 'customResourceProvider', isDangerous: true },
|
||||
|
||||
@@ -170,6 +170,66 @@ function BackendScriptApi(currentNote, apiParams) {
|
||||
*/
|
||||
this.toggleNoteInParent = cloningService.toggleNoteInParent;
|
||||
|
||||
/**
|
||||
* @typedef {object} CreateNoteAttribute
|
||||
* @property {string} type - attribute type - label, relation etc.
|
||||
* @property {string} name - attribute name
|
||||
* @property {string} [value] - attribute value
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create text note. See also createNewNote() for more options.
|
||||
*
|
||||
* @param {string} parentNoteId
|
||||
* @param {string} title
|
||||
* @param {string} content
|
||||
* @return {Promise<{note: Note, branch: Branch}>}
|
||||
*/
|
||||
this.createTextNote = async (parentNoteId, title, content = '') => await noteService.createNewNote({
|
||||
parentNoteId,
|
||||
title,
|
||||
content,
|
||||
type: 'text'
|
||||
});
|
||||
|
||||
/**
|
||||
* Create data note - data in this context means object serializable to JSON. Created note will be of type 'code' and
|
||||
* JSON MIME type. See also createNewNote() for more options.
|
||||
*
|
||||
* @param {string} parentNoteId
|
||||
* @param {string} title
|
||||
* @param {object} content
|
||||
* @return {Promise<{note: Note, branch: Branch}>}
|
||||
*/
|
||||
this.createDataNote = async (parentNoteId, title, content = {}) => await noteService.createNewNote({
|
||||
parentNoteId,
|
||||
title,
|
||||
content: JSON.stringify(content),
|
||||
type: 'code',
|
||||
mime: 'application/json'
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef {object} CreateNewNoteParams
|
||||
* @property {string} parentNoteId - MANDATORY
|
||||
* @property {string} title - MANDATORY
|
||||
* @property {string|buffer} content - MANDATORY
|
||||
* @property {string} type - text, code, file, image, search, book, relation-map - MANDATORY
|
||||
* @property {string} mime - value is derived from default mimes for type
|
||||
* @property {boolean} isProtected - default is false
|
||||
* @property {boolean} isExpanded - default is false
|
||||
* @property {string} prefix - default is empty string
|
||||
* @property {int} notePosition - default is last existing notePosition in a parent + 10
|
||||
*/
|
||||
|
||||
/**
|
||||
* @method
|
||||
*
|
||||
* @param {CreateNewNoteParams} [params]
|
||||
* @returns {Promise<{note: Note, branch: Branch}>} object contains newly created entities note and branch
|
||||
*/
|
||||
this.createNewNote = noteService.createNewNote;
|
||||
|
||||
/**
|
||||
* @typedef {object} CreateNoteAttribute
|
||||
* @property {string} type - attribute type - label, relation etc.
|
||||
@@ -195,25 +255,38 @@ function BackendScriptApi(currentNote, apiParams) {
|
||||
* @param {CreateNoteExtraOptions} [extraOptions={}]
|
||||
* @returns {Promise<{note: Note, branch: Branch}>} object contains newly created entities note and branch
|
||||
*/
|
||||
this.createNote = noteService.createNote;
|
||||
this.createNote = async (parentNoteId, title, content = "", extraOptions= {}) => {
|
||||
extraOptions.parentNoteId = parentNoteId;
|
||||
extraOptions.title = title;
|
||||
|
||||
/**
|
||||
* Creates new note according to given params and force all connected clients to refresh their tree.
|
||||
*
|
||||
* @method
|
||||
*
|
||||
* @param {string} parentNoteId - create new note under this parent
|
||||
* @param {string} title
|
||||
* @param {string} [content=""]
|
||||
* @param {CreateNoteExtraOptions} [extraOptions={}]
|
||||
* @returns {Promise<{note: Note, branch: Branch}>} object contains newly created entities note and branch
|
||||
*/
|
||||
this.createNoteAndRefresh = async function(parentNoteId, title, content, extraOptions) {
|
||||
const ret = await noteService.createNote(parentNoteId, title, content, extraOptions);
|
||||
const parentNote = await repository.getNote(parentNoteId);
|
||||
|
||||
ws.refreshTree();
|
||||
|
||||
return ret;
|
||||
// code note type can be inherited, otherwise text is default
|
||||
extraOptions.type = parentNote.type === 'code' ? 'code' : 'text';
|
||||
extraOptions.mime = parentNote.type === 'code' ? parentNote.mime : 'text/html';
|
||||
|
||||
if (extraOptions.json) {
|
||||
extraOptions.content = JSON.stringify(content || {}, null, '\t');
|
||||
extraOptions.type = 'code';
|
||||
extraOptions.mime = 'application/json';
|
||||
}
|
||||
else {
|
||||
extraOptions.content = content;
|
||||
}
|
||||
|
||||
const {note, branch} = await noteService.createNewNote(extraOptions);
|
||||
|
||||
for (const attr of extraOptions.attributes || []) {
|
||||
await attributeService.createAttribute({
|
||||
noteId: note.noteId,
|
||||
type: attr.type,
|
||||
name: attr.name,
|
||||
value: attr.value,
|
||||
isInheritable: !!attr.isInheritable
|
||||
});
|
||||
}
|
||||
|
||||
return {note, branch};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -240,6 +313,14 @@ function BackendScriptApi(currentNote, apiParams) {
|
||||
*/
|
||||
this.getDateNote = dateNoteService.getDateNote;
|
||||
|
||||
/**
|
||||
* Returns today's day note. If such note doesn't exist, it is created.
|
||||
*
|
||||
* @method
|
||||
* @returns {Promise<Note|null>}
|
||||
*/
|
||||
this.getTodayNote = dateNoteService.getTodayNote;
|
||||
|
||||
/**
|
||||
* Returns note for the first date of the week of the given date.
|
||||
*
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { buildDate:"2019-11-22T22:38:03+01:00", buildRevision: "7a2c7edd7e9b975bf64f732629e711379baecf48" };
|
||||
module.exports = { buildDate:"2019-12-04T22:40:31+01:00", buildRevision: "e0aabe4f9cb37b68dd70bbba6eae5dcc5893f075" };
|
||||
|
||||
@@ -11,7 +11,8 @@ const VIRTUAL_ATTRIBUTES = [
|
||||
"content",
|
||||
"type",
|
||||
"mime",
|
||||
"text"
|
||||
"text",
|
||||
"parentCount"
|
||||
];
|
||||
|
||||
module.exports = function(filters, selectedColumns = 'notes.*') {
|
||||
@@ -43,6 +44,12 @@ module.exports = function(filters, selectedColumns = 'notes.*') {
|
||||
|
||||
accessor = `${alias}.${property}`;
|
||||
}
|
||||
else if (property === 'parentCount') {
|
||||
// need to cast as string for the equality operator to work
|
||||
// for >= etc. it is cast again to DECIMAL
|
||||
// also cannot use COUNT() in WHERE so using subquery ...
|
||||
accessor = `CAST((SELECT COUNT(1) FROM branches WHERE branches.noteId = notes.noteId AND isDeleted = 0) AS STRING)`;
|
||||
}
|
||||
else {
|
||||
accessor = "notes." + property;
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ async function findExistencyIssues() {
|
||||
branches.parentNoteId,
|
||||
branches.noteId
|
||||
HAVING
|
||||
COUNT(*) > 1`,
|
||||
COUNT(1) > 1`,
|
||||
async ({noteId, parentNoteId}, autoFix) => {
|
||||
if (autoFix) {
|
||||
const branches = await repository.getEntities(
|
||||
@@ -311,6 +311,25 @@ async function findLogicIssues() {
|
||||
}
|
||||
});
|
||||
|
||||
await findAndFixIssues(`
|
||||
SELECT notes.noteId
|
||||
FROM notes
|
||||
LEFT JOIN note_contents USING(noteId)
|
||||
WHERE
|
||||
note_contents.noteId IS NULL`,
|
||||
async ({noteId}, autoFix) => {
|
||||
if (autoFix) {
|
||||
const note = await repository.getNote(noteId);
|
||||
// empty string might be wrong choice for some note types (and protected notes) but it's a best guess
|
||||
await note.setContent(note.isErased ? null : '');
|
||||
|
||||
logFix(`Note ${noteId} content was set to empty string since there was no corresponding row`);
|
||||
}
|
||||
else {
|
||||
logError(`Note ${noteId} content row does not exist`);
|
||||
}
|
||||
});
|
||||
|
||||
await findAndFixIssues(`
|
||||
SELECT noteId
|
||||
FROM notes
|
||||
@@ -321,6 +340,7 @@ async function findLogicIssues() {
|
||||
async ({noteId}, autoFix) => {
|
||||
if (autoFix) {
|
||||
const note = await repository.getNote(noteId);
|
||||
// empty string might be wrong choice for some note types (and protected notes) but it's a best guess
|
||||
await note.setContent('');
|
||||
|
||||
logFix(`Note ${noteId} content was set to empty string since it was null even though it is not deleted`);
|
||||
@@ -360,6 +380,25 @@ async function findLogicIssues() {
|
||||
}
|
||||
});
|
||||
|
||||
await findAndFixIssues(`
|
||||
SELECT note_revisions.noteRevisionId
|
||||
FROM note_revisions
|
||||
LEFT JOIN note_revision_contents USING(noteRevisionId)
|
||||
WHERE note_revision_contents.noteRevisionId IS NULL`,
|
||||
async ({noteRevisionId}, autoFix) => {
|
||||
if (autoFix) {
|
||||
const noteRevision = await repository.getNoteRevision(noteRevisionId);
|
||||
await noteRevision.setContent(null);
|
||||
noteRevision.isErased = true;
|
||||
await noteRevision.save();
|
||||
|
||||
logFix(`Note revision content ${noteRevisionId} was created and set to erased since it did not exist.`);
|
||||
}
|
||||
else {
|
||||
logError(`Note revision content ${noteRevisionId} does not exist`);
|
||||
}
|
||||
});
|
||||
|
||||
await findAndFixIssues(`
|
||||
SELECT noteRevisionId
|
||||
FROM note_revisions
|
||||
@@ -587,12 +626,31 @@ async function runAllChecks() {
|
||||
return !unrecoveredConsistencyErrors;
|
||||
}
|
||||
|
||||
async function showEntityStat(name, query) {
|
||||
const map = await sql.getMap(query);
|
||||
|
||||
map[0] = map[0] || 0;
|
||||
map[1] = map[1] || 0;
|
||||
|
||||
log.info(`${name} deleted: ${map[1]}, not deleted ${map[0]}`);
|
||||
}
|
||||
|
||||
async function runDbDiagnostics() {
|
||||
await showEntityStat("Notes", `SELECT isDeleted, count(1) FROM notes GROUP BY isDeleted`);
|
||||
await showEntityStat("Note revisions", `SELECT isErased, count(1) FROM note_revisions GROUP BY isErased`);
|
||||
await showEntityStat("Branches", `SELECT isDeleted, count(1) FROM branches GROUP BY isDeleted`);
|
||||
await showEntityStat("Attributes", `SELECT isDeleted, count(1) FROM attributes GROUP BY isDeleted`);
|
||||
await showEntityStat("API tokens", `SELECT isDeleted, count(1) FROM api_tokens GROUP BY isDeleted`);
|
||||
}
|
||||
|
||||
async function runChecks() {
|
||||
let elapsedTimeMs;
|
||||
|
||||
await syncMutexService.doExclusively(async () => {
|
||||
const startTime = new Date();
|
||||
|
||||
await runDbDiagnostics();
|
||||
|
||||
await runAllChecks();
|
||||
|
||||
elapsedTimeMs = Date.now() - startTime.getTime();
|
||||
@@ -624,7 +682,7 @@ sqlInit.dbReady.then(() => {
|
||||
setInterval(cls.wrap(runChecks), 60 * 60 * 1000);
|
||||
|
||||
// kickoff checks soon after startup (to not block the initial load)
|
||||
setTimeout(cls.wrap(runChecks), 10 * 1000);
|
||||
setTimeout(cls.wrap(runChecks), 20 * 1000);
|
||||
});
|
||||
|
||||
module.exports = {};
|
||||
+33
-26
@@ -13,12 +13,13 @@ const DATE_LABEL = 'dateNote';
|
||||
const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
|
||||
const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
|
||||
|
||||
async function createNote(parentNoteId, noteTitle, noteText) {
|
||||
return (await noteService.createNewNote(parentNoteId, {
|
||||
async function createNote(parentNoteId, noteTitle) {
|
||||
return (await noteService.createNewNote({
|
||||
parentNoteId: parentNoteId,
|
||||
title: noteTitle,
|
||||
content: noteText,
|
||||
target: 'into',
|
||||
isProtected: false
|
||||
content: '',
|
||||
isProtected: false,
|
||||
type: 'text'
|
||||
})).note;
|
||||
}
|
||||
|
||||
@@ -35,7 +36,8 @@ async function getRootCalendarNote() {
|
||||
let rootNote = await attributeService.getNoteWithLabel(CALENDAR_ROOT_LABEL);
|
||||
|
||||
if (!rootNote) {
|
||||
rootNote = (await noteService.createNewNote('root', {
|
||||
rootNote = (await noteService.createNewNote({
|
||||
parentNoteId: 'root',
|
||||
title: 'Calendar',
|
||||
target: 'into',
|
||||
isProtected: false
|
||||
@@ -63,15 +65,15 @@ async function getYearNote(dateStr, rootNote) {
|
||||
|
||||
if (!yearNote) {
|
||||
yearNote = await createNote(rootNote.noteId, yearStr);
|
||||
}
|
||||
|
||||
await attributeService.createLabel(yearNote.noteId, YEAR_LABEL, yearStr);
|
||||
await attributeService.createLabel(yearNote.noteId, 'sorted');
|
||||
await attributeService.createLabel(yearNote.noteId, YEAR_LABEL, yearStr);
|
||||
await attributeService.createLabel(yearNote.noteId, 'sorted');
|
||||
|
||||
const yearTemplateAttr = await rootNote.getOwnedAttribute('relation', 'yearTemplate');
|
||||
const yearTemplateAttr = await rootNote.getOwnedAttribute('relation', 'yearTemplate');
|
||||
|
||||
if (yearTemplateAttr) {
|
||||
await attributeService.createRelation(yearNote.noteId, 'template', yearTemplateAttr.value);
|
||||
if (yearTemplateAttr) {
|
||||
await attributeService.createRelation(yearNote.noteId, 'template', yearTemplateAttr.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +81,7 @@ async function getYearNote(dateStr, rootNote) {
|
||||
}
|
||||
|
||||
async function getMonthNoteTitle(rootNote, monthNumber, dateObj) {
|
||||
const pattern = await rootNote.getLabelValue("monthPattern") || "{monthNumberPadded} - {month}";
|
||||
const pattern = await rootNote.getOwnedLabelValue("monthPattern") || "{monthNumberPadded} - {month}";
|
||||
const monthName = MONTHS[dateObj.getMonth()];
|
||||
|
||||
return pattern
|
||||
@@ -109,15 +111,15 @@ async function getMonthNote(dateStr, rootNote) {
|
||||
const noteTitle = await getMonthNoteTitle(rootNote, monthNumber, dateObj);
|
||||
|
||||
monthNote = await createNote(yearNote.noteId, noteTitle);
|
||||
}
|
||||
|
||||
await attributeService.createLabel(monthNote.noteId, MONTH_LABEL, monthStr);
|
||||
await attributeService.createLabel(monthNote.noteId, 'sorted');
|
||||
await attributeService.createLabel(monthNote.noteId, MONTH_LABEL, monthStr);
|
||||
await attributeService.createLabel(monthNote.noteId, 'sorted');
|
||||
|
||||
const monthTemplateAttr = await rootNote.getOwnedAttribute('relation', 'monthTemplate');
|
||||
const monthTemplateAttr = await rootNote.getOwnedAttribute('relation', 'monthTemplate');
|
||||
|
||||
if (monthTemplateAttr) {
|
||||
await attributeService.createRelation(monthNote.noteId, 'template', monthTemplateAttr.value);
|
||||
if (monthTemplateAttr) {
|
||||
await attributeService.createRelation(monthNote.noteId, 'template', monthTemplateAttr.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +127,7 @@ async function getMonthNote(dateStr, rootNote) {
|
||||
}
|
||||
|
||||
async function getDateNoteTitle(rootNote, dayNumber, dateObj) {
|
||||
const pattern = await rootNote.getLabelValue("datePattern") || "{dayInMonthPadded} - {weekDay}";
|
||||
const pattern = await rootNote.getOwnedLabelValue("datePattern") || "{dayInMonthPadded} - {weekDay}";
|
||||
const weekDay = DAYS[dateObj.getDay()];
|
||||
|
||||
return pattern
|
||||
@@ -154,20 +156,24 @@ async function getDateNote(dateStr) {
|
||||
const noteTitle = await getDateNoteTitle(rootNote, dayNumber, dateObj);
|
||||
|
||||
dateNote = await createNote(monthNote.noteId, noteTitle);
|
||||
}
|
||||
|
||||
await attributeService.createLabel(dateNote.noteId, DATE_LABEL, dateStr.substr(0, 10));
|
||||
await attributeService.createLabel(dateNote.noteId, DATE_LABEL, dateStr.substr(0, 10));
|
||||
|
||||
const dateTemplateAttr = await rootNote.getOwnedAttribute('relation', 'dateTemplate');
|
||||
const dateTemplateAttr = await rootNote.getOwnedAttribute('relation', 'dateTemplate');
|
||||
|
||||
if (dateTemplateAttr) {
|
||||
await attributeService.createRelation(dateNote.noteId, 'template', dateTemplateAttr.value);
|
||||
if (dateTemplateAttr) {
|
||||
await attributeService.createRelation(dateNote.noteId, 'template', dateTemplateAttr.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dateNote;
|
||||
}
|
||||
|
||||
async function getTodayNote() {
|
||||
return await getDateNote(dateUtils.localNowDate());
|
||||
}
|
||||
|
||||
function getStartOfTheWeek(date, startOfTheWeek) {
|
||||
const day = date.getDay();
|
||||
let diff;
|
||||
@@ -200,5 +206,6 @@ module.exports = {
|
||||
getYearNote,
|
||||
getMonthNote,
|
||||
getWeekNote,
|
||||
getDateNote
|
||||
getDateNote,
|
||||
getTodayNote
|
||||
};
|
||||
@@ -16,7 +16,7 @@ async function exportToOpml(taskContext, branch, version, res) {
|
||||
const branch = await repository.getBranch(branchId);
|
||||
const note = await branch.getNote();
|
||||
|
||||
if (!note.isStringNote() || await note.hasLabel('excludeFromExport')) {
|
||||
if (!note.isStringNote() || await note.hasOwnedLabel('excludeFromExport')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,12 +79,17 @@ async function exportToTar(taskContext, branch, format, res) {
|
||||
async function getNoteMeta(branch, parentMeta, existingFileNames) {
|
||||
const note = await branch.getNote();
|
||||
|
||||
if (await note.hasLabel('excludeFromExport')) {
|
||||
if (await note.hasOwnedLabel('excludeFromExport')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const completeTitle = branch.prefix ? (branch.prefix + ' - ' + note.title) : note.title;
|
||||
const baseFileName = sanitize(completeTitle);
|
||||
let baseFileName = sanitize(completeTitle);
|
||||
|
||||
if (baseFileName.length > 200) { // actual limit is 256 bytes(!) but let's be conservative
|
||||
baseFileName = baseFileName.substr(0, 200);
|
||||
}
|
||||
|
||||
const notePath = parentMeta.notePath.concat([note.noteId]);
|
||||
|
||||
if (note.noteId in noteIdToMeta) {
|
||||
|
||||
@@ -27,7 +27,7 @@ eventService.subscribe(eventService.NOTE_TITLE_CHANGED, async note => {
|
||||
const parents = await note.getParentNotes();
|
||||
|
||||
for (const parent of parents) {
|
||||
if (await parent.hasLabel("sorted")) {
|
||||
if (await parent.hasOwnedLabel("sorted")) {
|
||||
await treeService.sortNotesAlphabetically(parent.noteId);
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ eventService.subscribe(eventService.CHILD_NOTE_CREATED, async ({ parentNote, chi
|
||||
async function processInverseRelations(entityName, entity, handler) {
|
||||
if (entityName === 'attributes' && entity.type === 'relation') {
|
||||
const note = await entity.getNote();
|
||||
const attributes = (await note.getAttributes(entity.name)).filter(relation => relation.type === 'relation-definition');
|
||||
const attributes = (await note.getOwnedAttributes(entity.name)).filter(relation => relation.type === 'relation-definition');
|
||||
|
||||
for (const attribute of attributes) {
|
||||
const definition = attribute.value;
|
||||
@@ -119,7 +119,7 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({ entityName, entity
|
||||
eventService.subscribe(eventService.ENTITY_DELETED, async ({ entityName, entity }) => {
|
||||
await processInverseRelations(entityName, entity, async (definition, note, targetNote) => {
|
||||
// if one inverse attribute is deleted then the other should be deleted as well
|
||||
const relations = await targetNote.getRelations(definition.inverseRelation);
|
||||
const relations = await targetNote.getOwnedRelations(definition.inverseRelation);
|
||||
let deletedSomething = false;
|
||||
|
||||
for (const relation of relations) {
|
||||
|
||||
@@ -12,9 +12,7 @@ const imageminGifLossy = require('imagemin-giflossy');
|
||||
const jimp = require('jimp');
|
||||
const imageType = require('image-type');
|
||||
const sanitizeFilename = require('sanitize-filename');
|
||||
const dateUtils = require('./date_utils');
|
||||
const noteRevisionService = require('./note_revisions.js');
|
||||
const NoteRevision = require("../entities/note_revision");
|
||||
|
||||
async function processImage(uploadBuffer, originalName, shrinkImageSwitch) {
|
||||
const origImageFormat = imageType(uploadBuffer);
|
||||
@@ -57,14 +55,17 @@ async function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSw
|
||||
|
||||
const parentNote = await repository.getNote(parentNoteId);
|
||||
|
||||
const {note} = await noteService.createNote(parentNoteId, fileName, buffer, {
|
||||
target: 'into',
|
||||
const {note} = await noteService.createNewNote({
|
||||
parentNoteId,
|
||||
title: fileName,
|
||||
content: buffer,
|
||||
type: 'image',
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
||||
mime: 'image/' + imageFormat.ext.toLowerCase(),
|
||||
attributes: [{ type: 'label', name: 'originalFileName', value: originalName }]
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable()
|
||||
});
|
||||
|
||||
await note.addLabel('originalFileName', originalName);
|
||||
|
||||
return {
|
||||
fileName,
|
||||
note,
|
||||
|
||||
@@ -3,6 +3,7 @@ const fileType = require('file-type');
|
||||
const stream = require('stream');
|
||||
const log = require("../log");
|
||||
const utils = require("../utils");
|
||||
const sql = require("../sql");
|
||||
const noteService = require("../notes");
|
||||
const imageService = require("../image");
|
||||
const protectedSessionService = require('../protected_session');
|
||||
@@ -11,7 +12,7 @@ const protectedSessionService = require('../protected_session');
|
||||
function parseDate(text) {
|
||||
// insert - and : to make it ISO format
|
||||
text = text.substr(0, 4) + "-" + text.substr(4, 2) + "-" + text.substr(6, 2)
|
||||
+ "T" + text.substr(9, 2) + ":" + text.substr(11, 2) + ":" + text.substr(13, 2) + "Z";
|
||||
+ " " + text.substr(9, 2) + ":" + text.substr(11, 2) + ":" + text.substr(13, 2) + ".000Z";
|
||||
|
||||
return text;
|
||||
}
|
||||
@@ -27,7 +28,10 @@ async function importEnex(taskContext, file, parentNote) {
|
||||
: file.originalname;
|
||||
|
||||
// root note is new note into all ENEX/notebook's notes will be imported
|
||||
const rootNote = (await noteService.createNote(parentNote.noteId, rootNoteTitle, "", {
|
||||
const rootNote = (await noteService.createNewNote({
|
||||
parentNoteId: parentNote.noteId,
|
||||
title: rootNoteTitle,
|
||||
content: "",
|
||||
type: 'text',
|
||||
mime: 'text/html',
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
||||
@@ -150,7 +154,7 @@ async function importEnex(taskContext, file, parentNote) {
|
||||
} else if (currentTag === 'created') {
|
||||
note.utcDateCreated = parseDate(text);
|
||||
} else if (currentTag === 'updated') {
|
||||
// updated is currently ignored since utcDateModified is updated automatically with each save
|
||||
note.utcDateModified = parseDate(text);
|
||||
} else if (currentTag === 'tag') {
|
||||
note.attributes.push({
|
||||
type: 'label',
|
||||
@@ -187,20 +191,48 @@ async function importEnex(taskContext, file, parentNote) {
|
||||
}
|
||||
});
|
||||
|
||||
async function updateDates(noteId, utcDateCreated, utcDateModified) {
|
||||
// it's difficult to force custom dateCreated and dateModified to Note entity so we do it post-creation with SQL
|
||||
await sql.execute(`
|
||||
UPDATE notes
|
||||
SET dateCreated = ?,
|
||||
utcDateCreated = ?,
|
||||
dateModified = ?,
|
||||
utcDateModified = ?
|
||||
WHERE noteId = ?`,
|
||||
[utcDateCreated, utcDateCreated, utcDateModified, utcDateModified, noteId]);
|
||||
|
||||
await sql.execute(`
|
||||
UPDATE note_contents
|
||||
SET utcDateModified = ?
|
||||
WHERE noteId = ?`,
|
||||
[utcDateModified, noteId]);
|
||||
}
|
||||
|
||||
async function saveNote() {
|
||||
// make a copy because stream continues with the next async call and note gets overwritten
|
||||
let {title, content, attributes, resources, utcDateCreated} = note;
|
||||
let {title, content, attributes, resources, utcDateCreated, utcDateModified} = note;
|
||||
|
||||
content = extractContent(content);
|
||||
|
||||
const noteEntity = (await noteService.createNote(rootNote.noteId, title, content, {
|
||||
attributes,
|
||||
const noteEntity = (await noteService.createNewNote({
|
||||
parentNoteId: rootNote.noteId,
|
||||
title,
|
||||
content,
|
||||
utcDateCreated,
|
||||
type: 'text',
|
||||
mime: 'text/html',
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
||||
})).note;
|
||||
|
||||
for (const attr of attributes) {
|
||||
await noteEntity.addAttribute(attr.type, attr.name, attr.value);
|
||||
}
|
||||
|
||||
utcDateCreated = utcDateCreated || noteEntity.utcDateCreated;
|
||||
// sometime date modified is not present in ENEX, then use date created
|
||||
utcDateModified = utcDateModified || utcDateCreated;
|
||||
|
||||
taskContext.increaseProgressCount();
|
||||
|
||||
let noteContent = await noteEntity.getContent();
|
||||
@@ -217,13 +249,21 @@ async function importEnex(taskContext, file, parentNote) {
|
||||
}
|
||||
|
||||
const createFileNote = async () => {
|
||||
const resourceNote = (await noteService.createNote(noteEntity.noteId, resource.title, resource.content, {
|
||||
attributes: resource.attributes,
|
||||
const resourceNote = (await noteService.createNewNote({
|
||||
parentNoteId: noteEntity.noteId,
|
||||
title: resource.title,
|
||||
content: resource.content,
|
||||
type: 'file',
|
||||
mime: resource.mime,
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
||||
})).note;
|
||||
|
||||
for (const attr of resource.attributes) {
|
||||
await noteEntity.addAttribute(attr.type, attr.name, attr.value);
|
||||
}
|
||||
|
||||
await updateDates(resourceNote.noteId, utcDateCreated, utcDateModified);
|
||||
|
||||
taskContext.increaseProgressCount();
|
||||
|
||||
const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`;
|
||||
@@ -235,7 +275,9 @@ async function importEnex(taskContext, file, parentNote) {
|
||||
try {
|
||||
const originalName = "image." + resource.mime.substr(6);
|
||||
|
||||
const {url} = await imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
|
||||
const {url, note: imageNote} = await imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
|
||||
|
||||
await updateDates(imageNote.noteId, utcDateCreated, utcDateModified);
|
||||
|
||||
const imageLink = `<img src="${url}">`;
|
||||
|
||||
@@ -257,6 +299,10 @@ async function importEnex(taskContext, file, parentNote) {
|
||||
|
||||
// save updated content with links to files/images
|
||||
await noteEntity.setContent(noteContent);
|
||||
|
||||
await noteService.scanForLinks(noteEntity.noteId);
|
||||
|
||||
await updateDates(noteEntity.noteId, utcDateCreated, utcDateModified);
|
||||
}
|
||||
|
||||
saxStream.on("closetag", async tag => {
|
||||
|
||||
@@ -44,8 +44,12 @@ async function importOpml(taskContext, fileBuffer, parentNote) {
|
||||
throw new Error("Unrecognized OPML version " + opmlVersion);
|
||||
}
|
||||
|
||||
const {note} = await noteService.createNote(parentNoteId, title, content, {
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
||||
const {note} = await noteService.createNewNote({
|
||||
parentNoteId,
|
||||
title,
|
||||
content,
|
||||
type: 'text',
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable()
|
||||
});
|
||||
|
||||
taskContext.increaseProgressCount();
|
||||
|
||||
@@ -41,16 +41,18 @@ async function importImage(file, parentNote, taskContext) {
|
||||
|
||||
async function importFile(taskContext, file, parentNote) {
|
||||
const originalName = file.originalname;
|
||||
const size = file.size;
|
||||
|
||||
const {note} = await noteService.createNote(parentNote.noteId, originalName, file.buffer, {
|
||||
target: 'into',
|
||||
const {note} = await noteService.createNewNote({
|
||||
parentNoteId: parentNote.noteId,
|
||||
title: originalName,
|
||||
content: file.buffer,
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
||||
type: 'file',
|
||||
mime: mimeService.getMime(originalName) || file.mimetype,
|
||||
attributes: [{ type: "label", name: "originalFileName", value: originalName }]
|
||||
mime: mimeService.getMime(originalName) || file.mimetype
|
||||
});
|
||||
|
||||
await note.addLabel("originalFileName", originalName);
|
||||
|
||||
taskContext.increaseProgressCount();
|
||||
|
||||
return note;
|
||||
@@ -62,7 +64,10 @@ async function importCodeNote(taskContext, file, parentNote) {
|
||||
const detectedMime = mimeService.getMime(file.originalname) || file.mimetype;
|
||||
const mime = mimeService.normalizeMimeType(detectedMime);
|
||||
|
||||
const {note} = await noteService.createNote(parentNote.noteId, title, content, {
|
||||
const {note} = await noteService.createNewNote({
|
||||
parentNoteId: parentNote.noteId,
|
||||
title,
|
||||
content,
|
||||
type: 'code',
|
||||
mime: mime,
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable()
|
||||
@@ -78,7 +83,10 @@ async function importPlainText(taskContext, file, parentNote) {
|
||||
const plainTextContent = file.buffer.toString("UTF-8");
|
||||
const htmlContent = convertTextToHtml(plainTextContent);
|
||||
|
||||
const {note} = await noteService.createNote(parentNote.noteId, title, htmlContent, {
|
||||
const {note} = await noteService.createNewNote({
|
||||
parentNoteId: parentNote.noteId,
|
||||
title,
|
||||
content: htmlContent,
|
||||
type: 'text',
|
||||
mime: 'text/html',
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
||||
@@ -118,7 +126,10 @@ async function importMarkdown(taskContext, file, parentNote) {
|
||||
|
||||
const title = getFileNameWithoutExtension(file.originalname);
|
||||
|
||||
const {note} = await noteService.createNote(parentNote.noteId, title, htmlContent, {
|
||||
const {note} = await noteService.createNewNote({
|
||||
parentNoteId: parentNote.noteId,
|
||||
title,
|
||||
content: htmlContent,
|
||||
type: 'text',
|
||||
mime: 'text/html',
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
||||
@@ -133,7 +144,10 @@ async function importHtml(taskContext, file, parentNote) {
|
||||
const title = getFileNameWithoutExtension(file.originalname);
|
||||
const content = file.buffer.toString("UTF-8");
|
||||
|
||||
const {note} = await noteService.createNote(parentNote.noteId, title, content, {
|
||||
const {note} = await noteService.createNewNote({
|
||||
parentNoteId: parentNote.noteId,
|
||||
title,
|
||||
content,
|
||||
type: 'text',
|
||||
mime: 'text/html',
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
||||
|
||||
@@ -147,7 +147,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attr.type === 'relation' && ['internal-link', 'image-link', 'relation-map-link'].includes(attr.name)) {
|
||||
if (attr.type === 'relation' && ['internalLink', 'imageLink', 'relationMapLink'].includes(attr.name)) {
|
||||
// these relations are created automatically and as such don't need to be duplicated in the import
|
||||
continue;
|
||||
}
|
||||
@@ -177,8 +177,11 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
|
||||
return;
|
||||
}
|
||||
|
||||
({note} = await noteService.createNote(parentNoteId, noteTitle, '', {
|
||||
noteId,
|
||||
({note} = await noteService.createNewNote({
|
||||
parentNoteId: parentNoteId,
|
||||
title: noteTitle,
|
||||
content: '',
|
||||
noteId: noteId,
|
||||
type: noteMeta ? noteMeta.type : 'text',
|
||||
mime: noteMeta ? noteMeta.mime : 'text/html',
|
||||
prefix: noteMeta ? noteMeta.prefix : '',
|
||||
@@ -258,7 +261,8 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
|
||||
content = content.toString("UTF-8");
|
||||
}
|
||||
|
||||
if ((noteMeta && noteMeta.format === 'markdown') || (!noteMeta && ['text/markdown', 'text/x-markdown'].includes(mime))) {
|
||||
if ((noteMeta && noteMeta.format === 'markdown')
|
||||
|| (!noteMeta && taskContext.data.textImportedAsText && ['text/markdown', 'text/x-markdown'].includes(mime))) {
|
||||
const parsed = mdReader.parse(content);
|
||||
content = mdWriter.render(parsed);
|
||||
}
|
||||
@@ -309,7 +313,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
|
||||
|
||||
if (type === 'relation-map' && noteMeta) {
|
||||
const relationMapLinks = (noteMeta.attributes || [])
|
||||
.filter(attr => attr.type === 'relation' && attr.name === 'relation-map-link');
|
||||
.filter(attr => attr.type === 'relation' && attr.name === 'relationMapLink');
|
||||
|
||||
// this will replace relation map links
|
||||
for (const link of relationMapLinks) {
|
||||
@@ -324,7 +328,10 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
|
||||
await note.setContent(content);
|
||||
}
|
||||
else {
|
||||
({note} = await noteService.createNote(parentNoteId, noteTitle, content, {
|
||||
({note} = await noteService.createNewNote({
|
||||
parentNoteId: parentNoteId,
|
||||
title: noteTitle,
|
||||
content: content,
|
||||
noteId,
|
||||
type,
|
||||
mime,
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
"use strict";
|
||||
|
||||
const optionService = require('./options');
|
||||
const log = require('./log');
|
||||
const utils = require('./utils');
|
||||
|
||||
const isMac = process.platform === "darwin";
|
||||
const isElectron = utils.isElectron();
|
||||
|
||||
const DEFAULT_KEYBOARD_ACTIONS = [
|
||||
{
|
||||
separator: "Note navigation"
|
||||
},
|
||||
{
|
||||
actionName: "BackInNoteHistory",
|
||||
// Mac has a different history navigation shortcuts - https://github.com/zadam/trilium/issues/376
|
||||
defaultShortcuts: isMac ? ["Meta+Left"] : ["Alt+Left"]
|
||||
},
|
||||
{
|
||||
actionName: "ForwardInNoteHistory",
|
||||
// Mac has a different history navigation shortcuts - https://github.com/zadam/trilium/issues/376
|
||||
defaultShortcuts: isMac ? ["Meta+Right"] : ["Alt+Right"]
|
||||
},
|
||||
{
|
||||
actionName: "JumpToNote",
|
||||
defaultShortcuts: ["CommandOrControl+J"],
|
||||
description: 'Open "Jump to note" dialog'
|
||||
},
|
||||
{
|
||||
actionName: "ScrollToActiveNote",
|
||||
defaultShortcuts: ["CommandOrControl+."]
|
||||
},
|
||||
{
|
||||
actionName: "SearchNotes",
|
||||
defaultShortcuts: ["CommandOrControl+S"]
|
||||
},
|
||||
{
|
||||
actionName: "SearchInSubtree",
|
||||
defaultShortcuts: ["CommandOrControl+Shift+S"],
|
||||
description: "Search for notes in the active note's subtree"
|
||||
},
|
||||
{
|
||||
actionName: "CollapseTree",
|
||||
defaultShortcuts: ["Alt+C"]
|
||||
},
|
||||
{
|
||||
actionName: "CollapseSubtree",
|
||||
defaultShortcuts: ["Alt+-"],
|
||||
description: "Collapses subtree of current note"
|
||||
},
|
||||
{
|
||||
actionName: "ActivateParentNote",
|
||||
defaultShortcuts: ["Backspace"],
|
||||
description: "Activates the parent note of currently active note"
|
||||
},
|
||||
{
|
||||
actionName: "SortChildNotes",
|
||||
defaultShortcuts: ["Alt+S"],
|
||||
description: "Sort child notes"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
separator: "Creating and moving notes"
|
||||
},
|
||||
{
|
||||
actionName: "CreateNoteAfter",
|
||||
defaultShortcuts: ["CommandOrControl+O"]
|
||||
},
|
||||
{
|
||||
actionName: "CreateNoteInto",
|
||||
defaultShortcuts: ["CommandOrControl+P"]
|
||||
},
|
||||
{
|
||||
actionName: "CreateNoteIntoDayNote",
|
||||
defaultShortcuts: ["global:CommandOrControl+Alt+P"],
|
||||
description: "Create and open subnote of a current day note"
|
||||
},
|
||||
{
|
||||
actionName: "DeleteNotes",
|
||||
defaultShortcuts: ["Delete"],
|
||||
description: "Delete note"
|
||||
},
|
||||
{
|
||||
actionName: "MoveNoteUp",
|
||||
defaultShortcuts: ["CommandOrControl+Up"],
|
||||
description: "Move note up"
|
||||
},
|
||||
{
|
||||
actionName: "MoveNoteDown",
|
||||
defaultShortcuts: ["CommandOrControl+Down"],
|
||||
description: "Move note down"
|
||||
},
|
||||
{
|
||||
actionName: "MoveNoteUpInHierarchy",
|
||||
defaultShortcuts: ["CommandOrControl+Left"],
|
||||
description: "Move note up in hierarchy"
|
||||
},
|
||||
{
|
||||
actionName: "MoveNoteDownInHierarchy",
|
||||
defaultShortcuts: ["CommandOrControl+Right"],
|
||||
description: "Move note down in hierarchy"
|
||||
},
|
||||
{
|
||||
actionName: "EditNoteTitle",
|
||||
defaultShortcuts: ["Enter"],
|
||||
description: "Jump from tree to the note detail and edit title"
|
||||
},
|
||||
{
|
||||
actionName: "EditBranchPrefix",
|
||||
defaultShortcuts: ["F2"],
|
||||
description: "Show Edit branch prefix dialog"
|
||||
},
|
||||
{
|
||||
actionName: "CloneNotesTo",
|
||||
defaultShortcuts: ["CommandOrControl+Shift+C"]
|
||||
},
|
||||
{
|
||||
actionName: "MoveNotesTo",
|
||||
defaultShortcuts: ["CommandOrControl+Shift+X"]
|
||||
},
|
||||
|
||||
{
|
||||
separator: "Note clipboard"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
actionName: "CopyNotesToClipboard",
|
||||
defaultShortcuts: ["CommandOrControl+C"],
|
||||
description: "Copy selected notes to the clipboard"
|
||||
},
|
||||
{
|
||||
actionName: "PasteNotesFromClipboard",
|
||||
defaultShortcuts: ["CommandOrControl+V"],
|
||||
description: "Paste notes from the clipboard into active note"
|
||||
},
|
||||
{
|
||||
actionName: "CutNotesToClipboard",
|
||||
defaultShortcuts: ["CommandOrControl+X"],
|
||||
description: "Cut selected notes to the clipboard"
|
||||
},
|
||||
{
|
||||
actionName: "SelectAllNotesInParent",
|
||||
defaultShortcuts: ["CommandOrControl+A"],
|
||||
description: "Select all notes from the current note level"
|
||||
},
|
||||
{
|
||||
actionName: "AddNoteAboveToSelection",
|
||||
defaultShortcuts: ["Shift+Up"],
|
||||
description: "Add note above to the selection"
|
||||
},
|
||||
{
|
||||
actionName: "AddNoteBelowToSelection",
|
||||
defaultShortcuts: ["Shift+Down"],
|
||||
description: "Add note above to the selection"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
separator: "Tabs"
|
||||
},
|
||||
{
|
||||
actionName: "OpenNewTab",
|
||||
defaultShortcuts: isElectron ? ["CommandOrControl+T"] : [],
|
||||
description: "Opens new tab"
|
||||
},
|
||||
{
|
||||
actionName: "CloseActiveTab",
|
||||
defaultShortcuts: isElectron ? ["CommandOrControl+W"] : [],
|
||||
description: "Closes active tab"
|
||||
},
|
||||
{
|
||||
actionName: "ActivateNextTab",
|
||||
defaultShortcuts: isElectron ? ["CommandOrControl+Tab"] : [],
|
||||
description: "Activates tab on the right"
|
||||
},
|
||||
{
|
||||
actionName: "ActivatePreviousTab",
|
||||
defaultShortcuts: isElectron ? ["CommandOrControl+Shift+Tab"] : [],
|
||||
description: "Activates tab on the left"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
separator: "Dialogs"
|
||||
},
|
||||
{
|
||||
actionName: "ShowAttributes",
|
||||
defaultShortcuts: ["Alt+A"],
|
||||
description: "Shows Attributes dialog"
|
||||
},
|
||||
{
|
||||
actionName: "ShowNoteInfo",
|
||||
defaultShortcuts: [],
|
||||
description: "Shows Note Info dialog"
|
||||
},
|
||||
{
|
||||
actionName: "ShowNoteSource",
|
||||
defaultShortcuts: [],
|
||||
description: "Shows Note Source dialog"
|
||||
},
|
||||
{
|
||||
actionName: "ShowLinkMap",
|
||||
defaultShortcuts: [],
|
||||
description: "Shows Link Map dialog"
|
||||
},
|
||||
{
|
||||
actionName: "ShowOptions",
|
||||
defaultShortcuts: [],
|
||||
description: "Shows Options dialog"
|
||||
},
|
||||
{
|
||||
actionName: "ShowNoteRevisions",
|
||||
defaultShortcuts: [],
|
||||
description: "Shows Note Revisions dialog"
|
||||
},
|
||||
{
|
||||
actionName: "ShowRecentChanges",
|
||||
defaultShortcuts: [],
|
||||
description: "Shows Recent Changes dialog"
|
||||
},
|
||||
{
|
||||
actionName: "ShowSQLConsole",
|
||||
defaultShortcuts: ["Alt+O"],
|
||||
description: "Shows SQL Console dialog"
|
||||
},
|
||||
{
|
||||
actionName: "ShowHelp",
|
||||
defaultShortcuts: ["F1"],
|
||||
description: "Shows built-in Help / cheatsheet"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
separator: "Text note operations"
|
||||
},
|
||||
|
||||
{
|
||||
actionName: "AddLinkToText",
|
||||
defaultShortcuts: ["CommandOrControl+L"],
|
||||
description: "Open dialog to add link to the text"
|
||||
},
|
||||
{
|
||||
actionName: "InsertDateTimeToText",
|
||||
defaultShortcuts: ["Alt+T"]
|
||||
},
|
||||
|
||||
{
|
||||
separator: "Other"
|
||||
},
|
||||
|
||||
{
|
||||
actionName: "PrintActiveNote",
|
||||
defaultShortcuts: []
|
||||
},
|
||||
{
|
||||
actionName: "RunActiveNote",
|
||||
defaultShortcuts: ["CommandOrControl+Enter"],
|
||||
description: "Run active JavaScript (frontend/backend) code note"
|
||||
},
|
||||
{
|
||||
actionName: "ToggleNoteHoisting",
|
||||
defaultShortcuts: ["Alt+H"],
|
||||
description: "Toggles note hoisting of active note"
|
||||
},
|
||||
{
|
||||
actionName: "ReloadFrontendApp",
|
||||
defaultShortcuts: ["F5", "CommandOrControl+R"]
|
||||
},
|
||||
{
|
||||
actionName: "OpenDevTools",
|
||||
defaultShortcuts: ["CommandOrControl+Shift+I"]
|
||||
},
|
||||
{
|
||||
actionName: "FindInText",
|
||||
defaultShortcuts: ["CommandOrControl+F"]
|
||||
},
|
||||
{
|
||||
actionName: "ToggleFullscreen",
|
||||
defaultShortcuts: ["F11"]
|
||||
},
|
||||
{
|
||||
actionName: "ToggleZenMode",
|
||||
defaultShortcuts: ["Alt+M"]
|
||||
},
|
||||
{
|
||||
actionName: "ZoomOut",
|
||||
defaultShortcuts: ["CommandOrControl+-"]
|
||||
},
|
||||
{
|
||||
actionName: "ZoomIn",
|
||||
defaultShortcuts: ["CommandOrControl+="]
|
||||
}
|
||||
];
|
||||
|
||||
const platformModifier = isMac ? 'Meta' : 'Ctrl';
|
||||
|
||||
for (const action of DEFAULT_KEYBOARD_ACTIONS) {
|
||||
if (action.defaultShortcuts) {
|
||||
action.defaultShortcuts = action.defaultShortcuts.map(shortcut => shortcut.replace("CommandOrControl", platformModifier));
|
||||
}
|
||||
}
|
||||
|
||||
async function getKeyboardActions() {
|
||||
const actions = JSON.parse(JSON.stringify(DEFAULT_KEYBOARD_ACTIONS));
|
||||
|
||||
for (const action of actions) {
|
||||
if (action.defaultShortcuts) {
|
||||
action.effectiveShortcuts = action.defaultShortcuts.slice();
|
||||
}
|
||||
}
|
||||
|
||||
for (const option of await optionService.getOptions()) {
|
||||
if (option.name.startsWith('keyboardShortcuts')) {
|
||||
const actionName = option.name.substr(17);
|
||||
|
||||
const action = actions.find(ea => ea.actionName === actionName);
|
||||
|
||||
if (action) {
|
||||
try {
|
||||
action.effectiveShortcuts = JSON.parse(option.value);
|
||||
}
|
||||
catch (e) {
|
||||
log.error(`Could not parse shortcuts for action ${actionName}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.info(`Keyboard action ${actionName} found in database, but not in action definition.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DEFAULT_KEYBOARD_ACTIONS,
|
||||
getKeyboardActions
|
||||
};
|
||||
@@ -43,9 +43,6 @@ async function migrate() {
|
||||
try {
|
||||
log.info("Attempting migration to version " + mig.dbVersion);
|
||||
|
||||
// needs to happen outside of the transaction (otherwise it's a NO-OP)
|
||||
await sql.execute("PRAGMA foreign_keys = OFF");
|
||||
|
||||
await sql.transactional(async () => {
|
||||
if (mig.type === 'sql') {
|
||||
const migrationSql = fs.readFileSync(resourceDir.MIGRATIONS_DIR + "/" + mig.file).toString('utf8');
|
||||
@@ -76,10 +73,6 @@ async function migrate() {
|
||||
|
||||
utils.crash();
|
||||
}
|
||||
finally {
|
||||
// make sure foreign keys are enabled even if migration script disables them
|
||||
await sql.execute("PRAGMA foreign_keys = ON");
|
||||
}
|
||||
}
|
||||
|
||||
if (await sqlInit.isDbUpToDate()) {
|
||||
|
||||
@@ -26,7 +26,10 @@ async function load() {
|
||||
noteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 0`);
|
||||
noteIds = Object.keys(noteTitles);
|
||||
|
||||
prefixes = await sql.getMap(`SELECT noteId || '-' || parentNoteId, prefix FROM branches WHERE prefix IS NOT NULL AND prefix != ''`);
|
||||
prefixes = await sql.getMap(`
|
||||
SELECT noteId || '-' || parentNoteId, prefix
|
||||
FROM branches
|
||||
WHERE isDeleted = 0 AND prefix IS NOT NULL AND prefix != ''`);
|
||||
|
||||
const branches = await sql.getRows(`SELECT branchId, noteId, parentNoteId FROM branches WHERE isDeleted = 0`);
|
||||
|
||||
|
||||
@@ -7,10 +7,6 @@ const dateUtils = require('../services/date_utils');
|
||||
* @param {Note} note
|
||||
*/
|
||||
async function protectNoteRevisions(note) {
|
||||
if (await note.hasLabel('disableVersioning')) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const revision of await note.getRevisions()) {
|
||||
if (note.isProtected !== revision.isProtected) {
|
||||
const content = await revision.getContent();
|
||||
@@ -30,6 +26,10 @@ async function protectNoteRevisions(note) {
|
||||
* @return {NoteRevision}
|
||||
*/
|
||||
async function createNoteRevision(note) {
|
||||
if (await note.hasLabel("disableVersioning")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const noteRevision = await new NoteRevision({
|
||||
noteId: note.noteId,
|
||||
// title and text should be decrypted now
|
||||
|
||||
+140
-140
@@ -16,29 +16,14 @@ const protectedSessionService = require('../services/protected_session');
|
||||
const log = require('../services/log');
|
||||
const noteRevisionService = require('../services/note_revisions');
|
||||
|
||||
async function getNewNotePosition(parentNoteId, noteData) {
|
||||
let newNotePos = 0;
|
||||
async function getNewNotePosition(parentNoteId) {
|
||||
const maxNotePos = await sql.getValue(`
|
||||
SELECT MAX(notePosition)
|
||||
FROM branches
|
||||
WHERE parentNoteId = ?
|
||||
AND isDeleted = 0`, [parentNoteId]);
|
||||
|
||||
if (noteData.target === 'into') {
|
||||
const maxNotePos = await sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [parentNoteId]);
|
||||
|
||||
newNotePos = maxNotePos === null ? 0 : maxNotePos + 10;
|
||||
}
|
||||
else if (noteData.target === 'after') {
|
||||
const afterNote = await sql.getRow('SELECT notePosition FROM branches WHERE branchId = ?', [noteData.target_branchId]);
|
||||
|
||||
newNotePos = afterNote.notePosition + 10;
|
||||
|
||||
// not updating utcDateModified to avoig having to sync whole rows
|
||||
await sql.execute('UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0',
|
||||
[parentNoteId, afterNote.notePosition]);
|
||||
|
||||
await syncTableService.addNoteReorderingSync(parentNoteId);
|
||||
}
|
||||
else {
|
||||
throw new Error('Unknown target: ' + noteData.target);
|
||||
}
|
||||
return newNotePos;
|
||||
return maxNotePos === null ? 0 : maxNotePos + 10;
|
||||
}
|
||||
|
||||
async function triggerChildNoteCreated(childNote, parentNote) {
|
||||
@@ -49,88 +34,92 @@ async function triggerNoteTitleChanged(note) {
|
||||
await eventService.emit(eventService.NOTE_TITLE_CHANGED, note);
|
||||
}
|
||||
|
||||
function deriveMime(type, mime) {
|
||||
if (!type) {
|
||||
throw new Error(`Note type is a required param`);
|
||||
}
|
||||
|
||||
if (mime) {
|
||||
return mime;
|
||||
}
|
||||
|
||||
if (type === 'text') {
|
||||
mime = 'text/html';
|
||||
} else if (type === 'code') {
|
||||
mime = 'text/plain';
|
||||
} else if (['relation-map', 'search'].includes(type)) {
|
||||
mime = 'application/json';
|
||||
} else if (type === 'render') {
|
||||
mime = '';
|
||||
}
|
||||
|
||||
return mime;
|
||||
}
|
||||
|
||||
async function copyChildAttributes(parentNote, childNote) {
|
||||
for (const attr of await parentNote.getOwnedAttributes()) {
|
||||
if (attr.name.startsWith("child:")) {
|
||||
await new Attribute({
|
||||
noteId: childNote.noteId,
|
||||
type: attr.type,
|
||||
name: attr.name.substr(6),
|
||||
value: attr.value,
|
||||
position: attr.position,
|
||||
isInheritable: attr.isInheritable
|
||||
}).save();
|
||||
|
||||
childNote.invalidateAttributeCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME: noteData has mandatory property "target", it might be better to add it as parameter to reflect this
|
||||
* Following object properties are mandatory:
|
||||
* - {string} parentNoteId
|
||||
* - {string} title
|
||||
* - {*} content
|
||||
* - {string} type - text, code, file, image, search, book, relation-map, render
|
||||
*
|
||||
* Following are optional (have defaults)
|
||||
* - {string} mime - value is derived from default mimes for type
|
||||
* - {boolean} isProtected - default is false
|
||||
* - {boolean} isExpanded - default is false
|
||||
* - {string} prefix - default is empty string
|
||||
* - {integer} notePosition - default is last existing notePosition in a parent + 10
|
||||
*
|
||||
* @param params
|
||||
* @return {Promise<{note: Note, branch: Branch}>}
|
||||
*/
|
||||
async function createNewNote(parentNoteId, noteData) {
|
||||
let newNotePos;
|
||||
|
||||
if (noteData.notePosition !== undefined) {
|
||||
newNotePos = noteData.notePosition;
|
||||
}
|
||||
else {
|
||||
newNotePos = await getNewNotePosition(parentNoteId, noteData);
|
||||
}
|
||||
|
||||
const parentNote = await repository.getNote(parentNoteId);
|
||||
async function createNewNote(params) {
|
||||
const parentNote = await repository.getNote(params.parentNoteId);
|
||||
|
||||
if (!parentNote) {
|
||||
throw new Error(`Parent note ${parentNoteId} not found.`);
|
||||
throw new Error(`Parent note ${params.parentNoteId} not found.`);
|
||||
}
|
||||
|
||||
if (!noteData.type) {
|
||||
if (parentNote.type === 'text' || parentNote.type === 'code') {
|
||||
noteData.type = parentNote.type;
|
||||
noteData.mime = parentNote.mime;
|
||||
}
|
||||
else {
|
||||
// inheriting note type makes sense only for text and code
|
||||
noteData.type = 'text';
|
||||
noteData.mime = 'text/html';
|
||||
}
|
||||
if (!params.title || params.title.trim().length === 0) {
|
||||
throw new Error(`Note title must not be empty`);
|
||||
}
|
||||
|
||||
if (!noteData.mime) {
|
||||
if (noteData.type === 'text') {
|
||||
noteData.mime = 'text/html';
|
||||
}
|
||||
else if (noteData.type === 'code') {
|
||||
noteData.mime = 'text/plain';
|
||||
}
|
||||
else if (noteData.type === 'relation-map' || noteData.type === 'search') {
|
||||
noteData.mime = 'application/json';
|
||||
}
|
||||
}
|
||||
|
||||
noteData.type = noteData.type || parentNote.type;
|
||||
noteData.mime = noteData.mime || parentNote.mime;
|
||||
|
||||
const note = await new Note({
|
||||
noteId: noteData.noteId, // optionally can force specific noteId
|
||||
title: noteData.title,
|
||||
isProtected: noteData.isProtected,
|
||||
type: noteData.type || 'text',
|
||||
mime: noteData.mime || 'text/html'
|
||||
noteId: params.noteId, // optionally can force specific noteId
|
||||
title: params.title,
|
||||
isProtected: !!params.isProtected,
|
||||
type: params.type,
|
||||
mime: deriveMime(params.type, params.mime)
|
||||
}).save();
|
||||
|
||||
if (note.isStringNote() || this.type === 'render') { // render to just make sure it's not null
|
||||
noteData.content = noteData.content || "";
|
||||
}
|
||||
|
||||
await note.setContent(noteData.content);
|
||||
await note.setContent(params.content);
|
||||
|
||||
const branch = await new Branch({
|
||||
noteId: note.noteId,
|
||||
parentNoteId: parentNoteId,
|
||||
notePosition: newNotePos,
|
||||
prefix: noteData.prefix,
|
||||
isExpanded: !!noteData.isExpanded
|
||||
parentNoteId: params.parentNoteId,
|
||||
notePosition: params.notePosition !== undefined ? params.notePosition : await getNewNotePosition(params.parentNoteId),
|
||||
prefix: params.prefix,
|
||||
isExpanded: !!params.isExpanded
|
||||
}).save();
|
||||
|
||||
for (const attr of await parentNote.getAttributes()) {
|
||||
if (attr.name.startsWith("child:")) {
|
||||
await new Attribute({
|
||||
noteId: note.noteId,
|
||||
type: attr.type,
|
||||
name: attr.name.substr(6),
|
||||
value: attr.value,
|
||||
position: attr.position,
|
||||
isInheritable: attr.isInheritable
|
||||
}).save();
|
||||
|
||||
note.invalidateAttributeCache();
|
||||
}
|
||||
}
|
||||
await copyChildAttributes(parentNote, note);
|
||||
|
||||
await triggerNoteTitleChanged(note);
|
||||
await triggerChildNoteCreated(note, parentNote);
|
||||
@@ -141,41 +130,61 @@ async function createNewNote(parentNoteId, noteData) {
|
||||
};
|
||||
}
|
||||
|
||||
async function createNote(parentNoteId, title, content = "", extraOptions = {}) {
|
||||
if (!parentNoteId) throw new Error("Empty parentNoteId");
|
||||
if (!title) throw new Error("Empty title");
|
||||
async function createNewNoteWithTarget(target, targetBranchId, params) {
|
||||
if (!params.type) {
|
||||
const parentNote = await repository.getNote(params.parentNoteId);
|
||||
|
||||
const noteData = {
|
||||
title: title,
|
||||
content: extraOptions.json ? JSON.stringify(content, null, '\t') : content,
|
||||
target: 'into',
|
||||
noteId: extraOptions.noteId,
|
||||
isProtected: !!extraOptions.isProtected,
|
||||
type: extraOptions.type,
|
||||
mime: extraOptions.mime,
|
||||
dateCreated: extraOptions.dateCreated,
|
||||
isExpanded: extraOptions.isExpanded,
|
||||
notePosition: extraOptions.notePosition
|
||||
};
|
||||
|
||||
if (extraOptions.json && !noteData.type) {
|
||||
noteData.type = "code";
|
||||
noteData.mime = "application/json";
|
||||
// code note type can be inherited, otherwise text is default
|
||||
params.type = parentNote.type === 'code' ? 'code' : 'text';
|
||||
params.mime = parentNote.type === 'code' ? parentNote.mime : 'text/html';
|
||||
}
|
||||
|
||||
const {note, branch} = await createNewNote(parentNoteId, noteData);
|
||||
|
||||
for (const attr of extraOptions.attributes || []) {
|
||||
await attributeService.createAttribute({
|
||||
noteId: note.noteId,
|
||||
type: attr.type,
|
||||
name: attr.name,
|
||||
value: attr.value,
|
||||
isInheritable: !!attr.isInheritable
|
||||
});
|
||||
if (target === 'into') {
|
||||
return await createNewNote(params);
|
||||
}
|
||||
else if (target === 'after') {
|
||||
const afterNote = await sql.getRow('SELECT notePosition FROM branches WHERE branchId = ?', [targetBranchId]);
|
||||
|
||||
return {note, branch};
|
||||
// not updating utcDateModified to avoig having to sync whole rows
|
||||
await sql.execute('UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0',
|
||||
[params.parentNoteId, afterNote.notePosition]);
|
||||
|
||||
params.notePosition = afterNote.notePosition + 10;
|
||||
|
||||
const retObject = await createNewNote(params);
|
||||
|
||||
await syncTableService.addNoteReorderingSync(params.parentNoteId);
|
||||
|
||||
return retObject;
|
||||
}
|
||||
else {
|
||||
throw new Error(`Unknown target ${target}`);
|
||||
}
|
||||
}
|
||||
|
||||
// methods below should be probably just backend API methods
|
||||
async function createJsonNote(parentNoteId, title, content = {}, params = {}) {
|
||||
params.parentNoteId = parentNoteId;
|
||||
params.title = title;
|
||||
|
||||
params.type = "code";
|
||||
params.mime = "application/json";
|
||||
|
||||
params.content = JSON.stringify(content, null, '\t');
|
||||
|
||||
return await createNewNote(params);
|
||||
}
|
||||
|
||||
async function createTextNote(parentNoteId, title, content = "", params = {}) {
|
||||
params.parentNoteId = parentNoteId;
|
||||
params.title = title;
|
||||
|
||||
params.type = "text";
|
||||
params.mime = "text/html";
|
||||
|
||||
params.content = content;
|
||||
|
||||
return await createNewNote(params);
|
||||
}
|
||||
|
||||
async function protectNoteRecursively(note, protect, taskContext) {
|
||||
@@ -209,7 +218,7 @@ function findImageLinks(content, foundLinks) {
|
||||
|
||||
while (match = re.exec(content)) {
|
||||
foundLinks.push({
|
||||
name: 'image-link',
|
||||
name: 'imageLink',
|
||||
value: match[1]
|
||||
});
|
||||
}
|
||||
@@ -225,7 +234,7 @@ function findInternalLinks(content, foundLinks) {
|
||||
|
||||
while (match = re.exec(content)) {
|
||||
foundLinks.push({
|
||||
name: 'internal-link',
|
||||
name: 'internalLink',
|
||||
value: match[1]
|
||||
});
|
||||
}
|
||||
@@ -239,7 +248,7 @@ function findRelationMapLinks(content, foundLinks) {
|
||||
|
||||
for (const note of obj.notes) {
|
||||
foundLinks.push({
|
||||
name: 'relation-map-link',
|
||||
name: 'relationMapLink',
|
||||
value: note.noteId
|
||||
});
|
||||
}
|
||||
@@ -308,8 +317,7 @@ async function saveLinks(note, content) {
|
||||
}
|
||||
|
||||
async function saveNoteRevision(note) {
|
||||
// files and images are immutable, they can't be updated
|
||||
// but we don't even version titles which is probably not correct
|
||||
// files and images are versioned separately
|
||||
if (note.type === 'file' || note.type === 'image' || await note.hasLabel('disableVersioning')) {
|
||||
return;
|
||||
}
|
||||
@@ -454,39 +462,31 @@ async function eraseDeletedNotes() {
|
||||
return;
|
||||
}
|
||||
|
||||
const utcNowDateTime = dateUtils.utcNowDateTime();
|
||||
const localNowDateTime = dateUtils.localNowDateTime();
|
||||
|
||||
// it's better to not use repository for this because it will complain about saving protected notes
|
||||
// out of protected session
|
||||
// out of protected session, also we don't want these changes to be synced (since they are done on all instances anyway)
|
||||
|
||||
// setting contentLength to zero would serve no benefit and it leaves potentially useful trail
|
||||
await sql.executeMany(`
|
||||
UPDATE notes
|
||||
SET isErased = 1,
|
||||
utcDateModified = '${utcNowDateTime}',
|
||||
dateModified = '${localNowDateTime}'
|
||||
SET isErased = 1
|
||||
WHERE noteId IN (???)`, noteIdsToErase);
|
||||
|
||||
await sql.executeMany(`
|
||||
UPDATE note_contents
|
||||
SET content = NULL,
|
||||
utcDateModified = '${utcNowDateTime}'
|
||||
SET content = NULL
|
||||
WHERE noteId IN (???)`, noteIdsToErase);
|
||||
|
||||
// deleting first contents since the WHERE relies on isErased = 0
|
||||
await sql.executeMany(`
|
||||
UPDATE note_revision_contents
|
||||
SET content = NULL,
|
||||
utcDateModified = '${utcNowDateTime}'
|
||||
SET content = NULL
|
||||
WHERE noteRevisionId IN
|
||||
(SELECT noteRevisionId FROM note_revisions WHERE isErased = 0 AND noteId IN ((???)))`, noteIdsToErase);
|
||||
(SELECT noteRevisionId FROM note_revisions WHERE isErased = 0 AND noteId IN (???))`, noteIdsToErase);
|
||||
|
||||
await sql.executeMany(`
|
||||
UPDATE note_revisions
|
||||
SET isErased = 1,
|
||||
title = NULL,
|
||||
utcDateModified = '${utcNowDateTime}'
|
||||
title = NULL
|
||||
WHERE isErased = 0 AND noteId IN (???)`, noteIdsToErase);
|
||||
}
|
||||
|
||||
@@ -514,7 +514,7 @@ async function duplicateNote(noteId, parentNoteId) {
|
||||
notePosition: origBranch ? origBranch.notePosition + 1 : null
|
||||
}).save();
|
||||
|
||||
for (const attribute of await origNote.getAttributes()) {
|
||||
for (const attribute of await origNote.getOwnedAttributes()) {
|
||||
const attr = new Attribute(attribute);
|
||||
attr.attributeId = undefined; // force creation of new attribute
|
||||
attr.noteId = newNote.noteId;
|
||||
@@ -537,10 +537,10 @@ sqlInit.dbReady.then(() => {
|
||||
|
||||
module.exports = {
|
||||
createNewNote,
|
||||
createNote,
|
||||
createNewNoteWithTarget,
|
||||
updateNote,
|
||||
deleteBranch,
|
||||
protectNoteRecursively,
|
||||
scanForLinks,
|
||||
duplicateNote
|
||||
};
|
||||
};
|
||||
|
||||
+4
-10
@@ -61,18 +61,12 @@ async function createOption(name, value, isSynced) {
|
||||
}).save();
|
||||
}
|
||||
|
||||
async function getOptions(allowedOptions) {
|
||||
let options = await require('./repository').getEntities("SELECT * FROM options ORDER BY name");
|
||||
|
||||
if (allowedOptions) {
|
||||
options = options.filter(opt => allowedOptions.includes(opt.name));
|
||||
}
|
||||
|
||||
return options;
|
||||
async function getOptions() {
|
||||
return await require('./repository').getEntities("SELECT * FROM options ORDER BY name");
|
||||
}
|
||||
|
||||
async function getOptionsMap(allowedOptions) {
|
||||
const options = await getOptions(allowedOptions);
|
||||
async function getOptionsMap() {
|
||||
const options = await getOptions();
|
||||
|
||||
return utils.toObject(options, opt => [opt.name, opt.value]);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ const appInfo = require('./app_info');
|
||||
const utils = require('./utils');
|
||||
const log = require('./log');
|
||||
const dateUtils = require('./date_utils');
|
||||
const keyboardActions = require('./keyboard_actions');
|
||||
|
||||
async function initDocumentOptions() {
|
||||
await optionService.createOption('documentId', utils.randomSecureToken(16), false);
|
||||
@@ -87,7 +88,9 @@ const defaultOptions = [
|
||||
async function initStartupOptions() {
|
||||
const optionsMap = await optionService.getOptionsMap();
|
||||
|
||||
for (const {name, value, isSynced} of defaultOptions) {
|
||||
const allDefaultOptions = defaultOptions.concat(getKeyboardDefaultOptions());
|
||||
|
||||
for (const {name, value, isSynced} of allDefaultOptions) {
|
||||
if (!(name in optionsMap)) {
|
||||
await optionService.createOption(name, value, isSynced);
|
||||
|
||||
@@ -96,6 +99,18 @@ async function initStartupOptions() {
|
||||
}
|
||||
}
|
||||
|
||||
function getKeyboardDefaultOptions() {
|
||||
return keyboardActions.DEFAULT_KEYBOARD_ACTIONS
|
||||
.filter(ka => !!ka.actionName)
|
||||
.map(ka => {
|
||||
return {
|
||||
name: "keyboardShortcuts" + ka.actionName,
|
||||
value: JSON.stringify(ka.defaultShortcuts),
|
||||
isSynced: false
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initDocumentOptions,
|
||||
initSyncedOptions,
|
||||
|
||||
@@ -37,7 +37,7 @@ function isProtectedSessionAvailable() {
|
||||
function decryptNotes(notes) {
|
||||
for (const note of notes) {
|
||||
if (note.isProtected) {
|
||||
note.title = decrypt(note.title);
|
||||
note.title = decryptString(note.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ async function executeBundle(bundle, apiParams = {}) {
|
||||
const ctx = new ScriptContext(bundle.allNotes, apiParams);
|
||||
|
||||
try {
|
||||
if (await bundle.note.hasLabel('manualTransactionHandling')) {
|
||||
if (await bundle.note.hasOwnedLabel('manualTransactionHandling')) {
|
||||
return await execute(ctx, script);
|
||||
}
|
||||
else {
|
||||
@@ -117,7 +117,7 @@ async function getScriptBundle(note, root = true, scriptEnv = null, includedNote
|
||||
return;
|
||||
}
|
||||
|
||||
if (!root && await note.hasLabel('disableInclusion')) {
|
||||
if (!root && await note.hasOwnedLabel('disableInclusion')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+20
-15
@@ -66,7 +66,7 @@ async function rollback() {
|
||||
}
|
||||
|
||||
async function getRow(query, params = []) {
|
||||
return await wrap(async db => db.get(query, ...params));
|
||||
return await wrap(async db => db.get(query, ...params), query);
|
||||
}
|
||||
|
||||
async function getRowOrNull(query, params = []) {
|
||||
@@ -106,7 +106,7 @@ async function getManyRows(query, params) {
|
||||
}
|
||||
|
||||
async function getRows(query, params = []) {
|
||||
return await wrap(async db => db.all(query, ...params));
|
||||
return await wrap(async db => db.all(query, ...params), query);
|
||||
}
|
||||
|
||||
async function getMap(query, params = []) {
|
||||
@@ -140,16 +140,7 @@ async function getColumn(query, params = []) {
|
||||
}
|
||||
|
||||
async function execute(query, params = []) {
|
||||
const startTimestamp = Date.now();
|
||||
|
||||
const result = await wrap(async db => db.run(query, ...params));
|
||||
|
||||
const milliseconds = Date.now() - startTimestamp;
|
||||
if (milliseconds >= 200) {
|
||||
log.info(`Slow query took ${milliseconds}ms: ${query}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
return await wrap(async db => db.run(query, ...params), query);
|
||||
}
|
||||
|
||||
async function executeMany(query, params) {
|
||||
@@ -158,14 +149,28 @@ async function executeMany(query, params) {
|
||||
}
|
||||
|
||||
async function executeScript(query) {
|
||||
return await wrap(async db => db.exec(query));
|
||||
return await wrap(async db => db.exec(query), query);
|
||||
}
|
||||
|
||||
async function wrap(func) {
|
||||
async function wrap(func, query) {
|
||||
const thisError = new Error();
|
||||
|
||||
try {
|
||||
return await func(dbConnection);
|
||||
const startTimestamp = Date.now();
|
||||
|
||||
const result = await func(dbConnection);
|
||||
|
||||
const milliseconds = Date.now() - startTimestamp;
|
||||
if (milliseconds >= 300) {
|
||||
if (query.includes("WITH RECURSIVE")) {
|
||||
log.info(`Slow recursive query took ${milliseconds}ms.`);
|
||||
}
|
||||
else {
|
||||
log.info(`Slow query took ${milliseconds}ms: ${query}`);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (e) {
|
||||
log.error("Error executing query. Inner exception: " + e.stack + thisError.stack);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user