Compare commits
218 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 631a75deec | |||
| 3f1d0e5872 | |||
| 0fe91d0184 | |||
| 2f711a12f8 | |||
| 64f32ba38f | |||
| 7db4859fb9 | |||
| eee9fcae5c | |||
| 9c4a976342 | |||
| e4a09c6207 | |||
| d467db2227 | |||
| b8d6ff0542 | |||
| a9b8e65c9b | |||
| bb8b563ece | |||
| 05a8ffb944 | |||
| 2502646a64 | |||
| 3d95d69f80 | |||
| df751f5d67 | |||
| 4f06b6de78 | |||
| d2177cd517 | |||
| 0affcf5ad2 | |||
| 7a416b107b | |||
| 1ff124dab7 | |||
| 4cb511bad0 | |||
| 73c8d145fa | |||
| ab79f24729 | |||
| cec71f65b3 | |||
| f75c008154 | |||
| 474baa7d95 | |||
| a155b6e8d5 | |||
| 229974e543 | |||
| 6fc19bfb93 | |||
| ccaa108faa | |||
| 0a72383495 | |||
| d389100611 | |||
| ea7257a5b2 | |||
| 0ebc947fbd | |||
| c89514f9bb | |||
| e0368e395c | |||
| 6986c201dd | |||
| bcf163f8a1 | |||
| 15aaead7b9 | |||
| d29c5c4758 | |||
| 81e2baeee5 | |||
| 4cececafc9 | |||
| 7c8e7a3f4b | |||
| 613d5f93e8 | |||
| 4f5b23fbf8 | |||
| a37b9cfc7b | |||
| 2bc18bc214 | |||
| f31a998c5d | |||
| 5552917533 | |||
| a9702aa6a2 | |||
| d1941cc650 | |||
| f98fa4098f | |||
| 5350496ed4 | |||
| b62d79044a | |||
| 0db3722ec2 | |||
| d47403c0e7 | |||
| 77311954a1 | |||
| b7cf4fe96b | |||
| 6d9b702d4c | |||
| 6e4c30571c | |||
| 5988776b7e | |||
| 384da60953 | |||
| 21fab412cb | |||
| eb4dfbad92 | |||
| aff9ce97ee | |||
| b0a3f828fb | |||
| 76f5736255 | |||
| a82066d899 | |||
| 45c5287d53 | |||
| dce54c7af3 | |||
| ee15db0ae1 | |||
| c48dbb0913 | |||
| 882ebdbd8f | |||
| 6f32d6fabe | |||
| 1e123f2390 | |||
| b29155775e | |||
| b821ed28fc | |||
| fdb8959aa1 | |||
| 7554cb057b | |||
| fab959539a | |||
| afe44a6fe8 | |||
| 7ed526beb7 | |||
| af695802e3 | |||
| 156f040880 | |||
| cf6f04defb | |||
| 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 | |||
| 38e7649ac3 | |||
| ff0245f05f | |||
| 7a2c7edd7e | |||
| 98c81faedb | |||
| cfb850acb2 | |||
| a16aaf7a81 | |||
| 465c3b87a7 | |||
| 0e5028acd3 | |||
| 00c295e4bf | |||
| 7084ed4fb1 | |||
| 587134c2f8 | |||
| 5fac2c7633 | |||
| 08a518479b | |||
| 522f71cb91 | |||
| bcdfb47939 | |||
| 667471e7bb | |||
| d357943ebb | |||
| 07043fb177 | |||
| 3c4ec7fe1a | |||
| 1f8d382b1f | |||
| 4bd7438fca | |||
| 0ae9c8da17 | |||
| 61e8cbbcba | |||
| 86c5dd6494 | |||
| f921562346 | |||
| c5acb7fc9b | |||
| 643d9077fc | |||
| b4709e8ee5 | |||
| a1181623b7 | |||
| 73a6c66379 | |||
| 1d5daa8dfd | |||
| 834e1f7253 | |||
| c141f4b2c0 | |||
| 1a87190f43 | |||
| a1262aaaf3 | |||
| 1838f097e5 | |||
| 95d0ad1cad | |||
| 767aaa18f4 | |||
| 8a7228146c | |||
| f9b5e473f2 | |||
| 3d294c5163 | |||
| 60231de0ed | |||
| 35e9df6170 | |||
| 13c0411533 | |||
| c5e040c4a4 | |||
| e00ab5dbf9 | |||
| de02e9e889 | |||
| e143becb7a |
+111
-114
@@ -1,8 +1,8 @@
|
||||
<?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>
|
||||
<ServerVersion>3.16.1</ServerVersion>
|
||||
</root>
|
||||
<schema id="2" parent="1" name="main">
|
||||
<Current>1</Current>
|
||||
@@ -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>
|
||||
<DataType>integer|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.14.0-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.14.0
|
||||
|
||||
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,
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<header>
|
||||
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>ApiToken<span class="signature">(apiTokenId, token, isDeleted, utcDateCreated)</span><span class="type-signature"></span></h2>
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>ApiToken<span class="signature">()</span><span class="type-signature"></span></h2>
|
||||
|
||||
<div class="class-description">ApiToken is an entity representing token used to authenticate against Trilium API from client applications. Currently used only by Trilium Sender.</div>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="ApiToken"><span class="type-signature"></span>new ApiToken<span class="signature">(apiTokenId, token, isDeleted, utcDateCreated)</span><span class="type-signature"></span></h4>
|
||||
<h4 class="name" id="ApiToken"><span class="type-signature"></span>new ApiToken<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
@@ -60,10 +60,15 @@
|
||||
|
||||
|
||||
|
||||
<h5>Parameters:</h5>
|
||||
|
||||
|
||||
|
||||
|
||||
<h5 class="subsection-title">Properties:</h5>
|
||||
|
||||
|
||||
|
||||
<table class="params">
|
||||
<table class="props">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -180,8 +185,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<header>
|
||||
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>Attribute<span class="signature">(attributeId, noteId, type, name, value, position, isInheritable, isDeleted, utcDateCreated, utcDateModified)</span><span class="type-signature"></span></h2>
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>Attribute<span class="signature">()</span><span class="type-signature"></span></h2>
|
||||
|
||||
<div class="class-description">Attribute is key value pair owned by a note.</div>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="Attribute"><span class="type-signature"></span>new Attribute<span class="signature">(attributeId, noteId, type, name, value, position, isInheritable, isDeleted, utcDateCreated, utcDateModified)</span><span class="type-signature"></span></h4>
|
||||
<h4 class="name" id="Attribute"><span class="type-signature"></span>new Attribute<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
@@ -60,10 +60,15 @@
|
||||
|
||||
|
||||
|
||||
<h5>Parameters:</h5>
|
||||
|
||||
|
||||
|
||||
|
||||
<h5 class="subsection-title">Properties:</h5>
|
||||
|
||||
|
||||
|
||||
<table class="params">
|
||||
<table class="props">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -318,8 +323,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<header>
|
||||
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>Branch<span class="signature">(branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, utcDateModified, utcDateCreated)</span><span class="type-signature"></span></h2>
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>Branch<span class="signature">()</span><span class="type-signature"></span></h2>
|
||||
|
||||
<div class="class-description">Branch represents note's placement in the tree - it's essentially pair of noteId and parentNoteId.
|
||||
Each note can have multiple (at least one) branches, meaning it can be placed into multiple places in the tree.</div>
|
||||
@@ -46,7 +46,7 @@ Each note can have multiple (at least one) branches, meaning it can be placed in
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="Branch"><span class="type-signature"></span>new Branch<span class="signature">(branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, utcDateModified, utcDateCreated)</span><span class="type-signature"></span></h4>
|
||||
<h4 class="name" id="Branch"><span class="type-signature"></span>new Branch<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
@@ -61,10 +61,15 @@ Each note can have multiple (at least one) branches, meaning it can be placed in
|
||||
|
||||
|
||||
|
||||
<h5>Parameters:</h5>
|
||||
|
||||
|
||||
|
||||
|
||||
<h5 class="subsection-title">Properties:</h5>
|
||||
|
||||
|
||||
|
||||
<table class="params">
|
||||
<table class="props">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -296,8 +301,6 @@ Each note can have multiple (at least one) branches, meaning it can be placed in
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
+2360
-71
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,7 @@
|
||||
|
||||
<header>
|
||||
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>NoteRevision<span class="signature">(noteRevisionId, noteId, type, mime, title, isProtected, dateLastEdited, dateCreated, utcDateLastEdited, utcDateCreated, utcDateModified)</span><span class="type-signature"></span></h2>
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>NoteRevision<span class="signature">()</span><span class="type-signature"></span></h2>
|
||||
|
||||
<div class="class-description">NoteRevision represents snapshot of note's title and content at some point in the past. It's used for seamless note versioning.</div>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="NoteRevision"><span class="type-signature"></span>new NoteRevision<span class="signature">(noteRevisionId, noteId, type, mime, title, isProtected, dateLastEdited, dateCreated, utcDateLastEdited, utcDateCreated, utcDateModified)</span><span class="type-signature"></span></h4>
|
||||
<h4 class="name" id="NoteRevision"><span class="type-signature"></span>new NoteRevision<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
@@ -60,10 +60,15 @@
|
||||
|
||||
|
||||
|
||||
<h5>Parameters:</h5>
|
||||
|
||||
|
||||
|
||||
|
||||
<h5 class="subsection-title">Properties:</h5>
|
||||
|
||||
|
||||
|
||||
<table class="params">
|
||||
<table class="props">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -193,6 +198,52 @@
|
||||
|
||||
|
||||
|
||||
<td class="description last"></td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>contentLength</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">int</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last"></td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="name"><code>isErased</code></td>
|
||||
|
||||
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">boolean</span>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<td class="description last"></td>
|
||||
</tr>
|
||||
|
||||
@@ -206,7 +257,7 @@
|
||||
<td class="type">
|
||||
|
||||
|
||||
<span class="param-type">string</span>
|
||||
<span class="param-type">boolean</span>
|
||||
|
||||
|
||||
|
||||
@@ -341,8 +392,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
@@ -372,7 +421,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line33">line 33</a>
|
||||
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line35">line 35</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -487,7 +536,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line67">line 67</a>
|
||||
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line69">line 69</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -589,7 +638,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line53">line 53</a>
|
||||
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line55">line 55</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
@@ -695,7 +744,7 @@
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line102">line 102</a>
|
||||
<a href="entities_note_revision.js.html">entities/note_revision.js</a>, <a href="entities_note_revision.js.html#line104">line 104</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<header>
|
||||
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>Option<span class="signature">(name, value, isSynced, utcDateModified, utcDateCreated)</span><span class="type-signature"></span></h2>
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>Option<span class="signature">()</span><span class="type-signature"></span></h2>
|
||||
|
||||
<div class="class-description">Option represents name-value pair, either directly configurable by the user or some system property.</div>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="Option"><span class="type-signature"></span>new Option<span class="signature">(name, value, isSynced, utcDateModified, utcDateCreated)</span><span class="type-signature"></span></h4>
|
||||
<h4 class="name" id="Option"><span class="type-signature"></span>new Option<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
@@ -60,10 +60,15 @@
|
||||
|
||||
|
||||
|
||||
<h5>Parameters:</h5>
|
||||
|
||||
|
||||
|
||||
|
||||
<h5 class="subsection-title">Properties:</h5>
|
||||
|
||||
|
||||
|
||||
<table class="params">
|
||||
<table class="props">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -203,8 +208,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<header>
|
||||
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>RecentNote<span class="signature">(noteId, notePath, isDeleted, utcDateModified)</span><span class="type-signature"></span></h2>
|
||||
<h2><span class="attribs"><span class="type-signature"></span></span>RecentNote<span class="signature">()</span><span class="type-signature"></span></h2>
|
||||
|
||||
<div class="class-description">RecentNote represents recently visited note.</div>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="RecentNote"><span class="type-signature"></span>new RecentNote<span class="signature">(noteId, notePath, isDeleted, utcDateModified)</span><span class="type-signature"></span></h4>
|
||||
<h4 class="name" id="RecentNote"><span class="type-signature"></span>new RecentNote<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
@@ -60,10 +60,15 @@
|
||||
|
||||
|
||||
|
||||
<h5>Parameters:</h5>
|
||||
|
||||
|
||||
|
||||
|
||||
<h5 class="subsection-title">Properties:</h5>
|
||||
|
||||
|
||||
|
||||
<table class="params">
|
||||
<table class="props">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -180,8 +185,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
@@ -34,10 +34,10 @@ const dateUtils = require('../services/date_utils');
|
||||
/**
|
||||
* ApiToken is an entity representing token used to authenticate against Trilium API from client applications. Currently used only by Trilium Sender.
|
||||
*
|
||||
* @param {string} apiTokenId - primary key
|
||||
* @param {string} token
|
||||
* @param {boolean} isDeleted - true if API token is deleted
|
||||
* @param {string} utcDateCreated
|
||||
* @property {string} apiTokenId - primary key
|
||||
* @property {string} token
|
||||
* @property {boolean} isDeleted - true if API token is deleted
|
||||
* @property {string} utcDateCreated
|
||||
*
|
||||
* @extends Entity
|
||||
*/
|
||||
|
||||
@@ -36,16 +36,16 @@ const sql = require('../services/sql');
|
||||
/**
|
||||
* Attribute is key value pair owned by a note.
|
||||
*
|
||||
* @param {string} attributeId
|
||||
* @param {string} noteId
|
||||
* @param {string} type
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
* @param {int} position
|
||||
* @param {boolean} isInheritable
|
||||
* @param {boolean} isDeleted
|
||||
* @param {string} utcDateCreated
|
||||
* @param {string} utcDateModified
|
||||
* @property {string} attributeId
|
||||
* @property {string} noteId
|
||||
* @property {string} type
|
||||
* @property {string} name
|
||||
* @property {string} value
|
||||
* @property {int} position
|
||||
* @property {boolean} isInheritable
|
||||
* @property {boolean} isDeleted
|
||||
* @property {string} utcDateCreated
|
||||
* @property {string} utcDateModified
|
||||
*
|
||||
* @extends Entity
|
||||
*/
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,15 +37,15 @@ const sql = require('../services/sql');
|
||||
* Branch represents note's placement in the tree - it's essentially pair of noteId and parentNoteId.
|
||||
* Each note can have multiple (at least one) branches, meaning it can be placed into multiple places in the tree.
|
||||
*
|
||||
* @param {string} branchId - primary key
|
||||
* @param {string} noteId
|
||||
* @param {string} parentNoteId
|
||||
* @param {int} notePosition
|
||||
* @param {string} prefix
|
||||
* @param {boolean} isExpanded
|
||||
* @param {boolean} isDeleted
|
||||
* @param {string} utcDateModified
|
||||
* @param {string} utcDateCreated
|
||||
* @property {string} branchId - primary key
|
||||
* @property {string} noteId
|
||||
* @property {string} parentNoteId
|
||||
* @property {int} notePosition
|
||||
* @property {string} prefix
|
||||
* @property {boolean} isExpanded
|
||||
* @property {boolean} isDeleted
|
||||
* @property {string} utcDateModified
|
||||
* @property {string} utcDateCreated
|
||||
*
|
||||
* @extends Entity
|
||||
*/
|
||||
|
||||
@@ -49,6 +49,7 @@ const RELATION_DEFINITION = 'relation-definition';
|
||||
* @property {string} type - one of "text", "code", "file" or "render"
|
||||
* @property {string} mime - MIME type, e.g. "text/html"
|
||||
* @property {string} title - note title
|
||||
* @property {int} contentLength - length of content
|
||||
* @property {boolean} isProtected - true if note is protected
|
||||
* @property {boolean} isDeleted - true if note is deleted
|
||||
* @property {boolean} isErased - true if note's content is erased after it has been deleted
|
||||
@@ -141,8 +142,13 @@ 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;
|
||||
await this.save();
|
||||
|
||||
this.content = content;
|
||||
@@ -218,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -288,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,6 +367,7 @@ class Note extends Entity {
|
||||
*/
|
||||
invalidateAttributeCache() {
|
||||
this.__attributeCache = null;
|
||||
this.__ownedAttributeCache = null;
|
||||
}
|
||||
|
||||
/** @returns {Promise<void>} */
|
||||
@@ -337,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
|
||||
@@ -360,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);
|
||||
|
||||
@@ -403,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
|
||||
@@ -417,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);
|
||||
@@ -425,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.
|
||||
*
|
||||
@@ -452,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) {
|
||||
@@ -486,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)) {
|
||||
@@ -498,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)
|
||||
@@ -544,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.
|
||||
*
|
||||
@@ -697,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]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -754,6 +890,36 @@ class Note extends Entity {
|
||||
AND parent_notes.isDeleted = 0`, [this.noteId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise<string[][]>} - array of notePaths (each represented by array of noteIds constituting the particular note path)
|
||||
*/
|
||||
async getAllNotePaths() {
|
||||
if (this.noteId === 'root') {
|
||||
return [['root']];
|
||||
}
|
||||
|
||||
const notePaths = [];
|
||||
|
||||
for (const parentNote of await this.getParentNotes()) {
|
||||
for (const parentPath of await parentNote.getAllNotePaths()) {
|
||||
parentPath.push(this.noteId);
|
||||
notePaths.push(parentPath);
|
||||
}
|
||||
}
|
||||
|
||||
return notePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
@@ -767,6 +933,10 @@ class Note extends Entity {
|
||||
this.utcDateCreated = dateUtils.utcNowDateTime();
|
||||
}
|
||||
|
||||
if (this.contentLength === undefined) {
|
||||
this.contentLength = -1;
|
||||
}
|
||||
|
||||
super.beforeSaving();
|
||||
|
||||
if (this.isChanged) {
|
||||
@@ -789,7 +959,9 @@ class Note extends Entity {
|
||||
|
||||
delete pojo.isContentAvailable;
|
||||
delete pojo.__attributeCache;
|
||||
delete pojo.__ownedAttributeCache;
|
||||
delete pojo.content;
|
||||
/** zero references to contentHash, probably can be removed */
|
||||
delete pojo.contentHash;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,24 +39,26 @@ const syncTableService = require('../services/sync_table');
|
||||
/**
|
||||
* NoteRevision represents snapshot of note's title and content at some point in the past. It's used for seamless note versioning.
|
||||
*
|
||||
* @param {string} noteRevisionId
|
||||
* @param {string} noteId
|
||||
* @param {string} type
|
||||
* @param {string} mime
|
||||
* @param {string} title
|
||||
* @param {string} isProtected
|
||||
* @param {string} dateLastEdited
|
||||
* @param {string} dateCreated
|
||||
* @param {string} utcDateLastEdited
|
||||
* @param {string} utcDateCreated
|
||||
* @param {string} utcDateModified
|
||||
* @property {string} noteRevisionId
|
||||
* @property {string} noteId
|
||||
* @property {string} type
|
||||
* @property {string} mime
|
||||
* @property {string} title
|
||||
* @property {int} contentLength
|
||||
* @property {boolean} isErased
|
||||
* @property {boolean} isProtected
|
||||
* @property {string} dateLastEdited
|
||||
* @property {string} dateCreated
|
||||
* @property {string} utcDateLastEdited
|
||||
* @property {string} utcDateCreated
|
||||
* @property {string} utcDateModified
|
||||
*
|
||||
* @extends Entity
|
||||
*/
|
||||
class NoteRevision extends Entity {
|
||||
static get entityName() { return "note_revisions"; }
|
||||
static get primaryKeyName() { return "noteRevisionId"; }
|
||||
static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "contentLength", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified"]; }
|
||||
static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "contentLength", "isErased", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified"]; }
|
||||
|
||||
constructor(row) {
|
||||
super(row);
|
||||
@@ -130,7 +132,7 @@ class NoteRevision extends Entity {
|
||||
async setContent(content) {
|
||||
// force updating note itself so that utcDateModified is represented correctly even for the content
|
||||
this.forcedChange = true;
|
||||
this.contentLength = content.length;
|
||||
this.contentLength = content === null ? 0 : content.length;
|
||||
await this.save();
|
||||
|
||||
this.content = content;
|
||||
@@ -156,6 +158,14 @@ class NoteRevision extends Entity {
|
||||
await syncTableService.addNoteRevisionContentSync(this.noteRevisionId);
|
||||
}
|
||||
|
||||
beforeSaving() {
|
||||
super.beforeSaving();
|
||||
|
||||
if (this.isChanged) {
|
||||
this.utcDateModified = dateUtils.utcNowDateTime();
|
||||
}
|
||||
}
|
||||
|
||||
// cannot be static!
|
||||
updatePojo(pojo) {
|
||||
if (pojo.isProtected) {
|
||||
|
||||
@@ -34,11 +34,11 @@ const dateUtils = require('../services/date_utils');
|
||||
/**
|
||||
* Option represents name-value pair, either directly configurable by the user or some system property.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
* @param {boolean} isSynced
|
||||
* @param {string} utcDateModified
|
||||
* @param {string} utcDateCreated
|
||||
* @property {string} name
|
||||
* @property {string} value
|
||||
* @property {boolean} isSynced
|
||||
* @property {string} utcDateModified
|
||||
* @property {string} utcDateCreated
|
||||
*
|
||||
* @extends Entity
|
||||
*/
|
||||
|
||||
@@ -34,10 +34,10 @@ const dateUtils = require('../services/date_utils');
|
||||
/**
|
||||
* RecentNote represents recently visited note.
|
||||
*
|
||||
* @param {string} noteId
|
||||
* @param {string} notePath
|
||||
* @param {boolean} isDeleted
|
||||
* @param {string} utcDateModified
|
||||
* @property {string} noteId
|
||||
* @property {string} notePath
|
||||
* @property {boolean} isDeleted
|
||||
* @property {string} utcDateModified
|
||||
*
|
||||
* @extends Entity
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -4975,6 +4975,90 @@ Internally this serializes the anonymous function into string and sends it to ba
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="waitUntilSynced"><span class="type-signature"></span>waitUntilSynced<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_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line389">line 389</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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>
|
||||
+642
-489
File diff suppressed because it is too large
Load Diff
@@ -47,7 +47,7 @@ class Branch {
|
||||
|
||||
/** @returns {NoteShort} */
|
||||
async getNote() {
|
||||
return await this.treeCache.getNote(this.noteId);
|
||||
return this.treeCache.getNote(this.noteId);
|
||||
}
|
||||
|
||||
/** @returns {boolean} true if it's top level, meaning its parent is root note */
|
||||
|
||||
@@ -49,6 +49,8 @@ class NoteShort {
|
||||
this.noteId = row.noteId;
|
||||
/** @param {string} */
|
||||
this.title = row.title;
|
||||
/** @param {int} */
|
||||
this.contentLength = row.contentLength;
|
||||
/** @param {boolean} */
|
||||
this.isProtected = row.isProtected;
|
||||
/** @param {string} one of 'text', 'code', 'file' or 'render' */
|
||||
@@ -61,6 +63,8 @@ class NoteShort {
|
||||
this.archived = row.archived;
|
||||
/** @param {string} */
|
||||
this.cssClass = row.cssClass;
|
||||
/** @param {string} */
|
||||
this.iconClass = row.iconClass;
|
||||
|
||||
/** @type {string[]} */
|
||||
this.parents = [];
|
||||
@@ -178,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,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>
|
||||
@@ -115,7 +115,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
|
||||
|
||||
const button = $('<button>')
|
||||
.addClass("btn btn-sm")
|
||||
.click(opts.action);
|
||||
.on('click', opts.action);
|
||||
|
||||
if (opts.icon) {
|
||||
button.append($("<span>").addClass("bx bx-" + opts.icon))
|
||||
@@ -410,6 +410,11 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
|
||||
* @param {function} handler
|
||||
*/
|
||||
this.bindGlobalShortcut = utils.bindGlobalShortcut;
|
||||
|
||||
/**
|
||||
* @method
|
||||
*/
|
||||
this.waitUntilSynced = ws.waitForMaxKnownSyncId;
|
||||
}
|
||||
|
||||
export default FrontendScriptApi;</code></pre>
|
||||
|
||||
@@ -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>
|
||||
+13
-85
@@ -1,85 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const {app, globalShortcut, BrowserWindow} = require('electron');
|
||||
const path = require('path');
|
||||
const log = require('./src/services/log');
|
||||
const {app, globalShortcut} = require('electron');
|
||||
const sqlInit = require('./src/services/sql_init');
|
||||
const cls = require('./src/services/cls');
|
||||
const url = require("url");
|
||||
const port = require('./src/services/port');
|
||||
const env = require('./src/services/env');
|
||||
const appIconService = require('./src/services/app_icon');
|
||||
const windowStateKeeper = require('electron-window-state');
|
||||
const windowService = require('./src/services/window');
|
||||
|
||||
// Adds debug features like hotkeys for triggering dev tools and reload
|
||||
require('electron-debug')();
|
||||
|
||||
appIconService.installLocalAppIcon();
|
||||
|
||||
// Prevent window being garbage collected
|
||||
let mainWindow;
|
||||
|
||||
require('electron-dl')({ saveAs: true });
|
||||
|
||||
function onClosed() {
|
||||
// Dereference the window
|
||||
// For multiple windows store them in an array
|
||||
mainWindow = null;
|
||||
}
|
||||
|
||||
async function createMainWindow() {
|
||||
await sqlInit.dbConnection;
|
||||
|
||||
// if schema doesn't exist -> setup process
|
||||
// if schema exists, then we need to wait until the migration process is finished
|
||||
if (await sqlInit.schemaExists()) {
|
||||
await sqlInit.dbReady;
|
||||
}
|
||||
|
||||
const mainWindowState = windowStateKeeper({
|
||||
// default window width & height so it's usable on 1600 * 900 display (including some extra panels etc.)
|
||||
defaultWidth: 1200,
|
||||
defaultHeight: 800
|
||||
});
|
||||
|
||||
const win = new BrowserWindow({
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
width: mainWindowState.width,
|
||||
height: mainWindowState.height,
|
||||
title: 'Trilium Notes',
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
},
|
||||
icon: path.join(__dirname, 'images/app-icons/png/256x256' + (env.isDev() ? '-dev' : '') + '.png')
|
||||
});
|
||||
|
||||
mainWindowState.manage(win);
|
||||
|
||||
win.setMenuBarVisibility(false);
|
||||
win.loadURL('http://127.0.0.1:' + await port);
|
||||
win.on('closed', onClosed);
|
||||
|
||||
win.webContents.on('new-window', (e, url) => {
|
||||
if (url !== win.webContents.getURL()) {
|
||||
e.preventDefault();
|
||||
require('electron').shell.openExternal(url);
|
||||
}
|
||||
});
|
||||
|
||||
// prevent drag & drop to navigate away from trilium
|
||||
win.webContents.on('will-navigate', (ev, targetUrl) => {
|
||||
const parsedUrl = url.parse(targetUrl);
|
||||
|
||||
// we still need to allow internal redirects from setup and migration pages
|
||||
if (!['localhost', '127.0.0.1'].includes(parsedUrl.hostname) || (parsedUrl.path && parsedUrl.path !== '/')) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
@@ -89,27 +21,23 @@ app.on('window-all-closed', () => {
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
if (!mainWindow) {
|
||||
mainWindow = createMainWindow();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('ready', async () => {
|
||||
app.setAppUserModelId('com.github.zadam.trilium');
|
||||
|
||||
mainWindow = await createMainWindow();
|
||||
await sqlInit.dbConnection;
|
||||
|
||||
const result = globalShortcut.register('CommandOrControl+Alt+P', cls.wrap(async () => {
|
||||
// window may be hidden / not in focus
|
||||
mainWindow.focus();
|
||||
// if schema doesn't exist -> setup process
|
||||
// if schema exists, then we need to wait until the migration process is finished
|
||||
if (await sqlInit.schemaExists()) {
|
||||
await sqlInit.dbReady;
|
||||
|
||||
mainWindow.webContents.send('create-day-sub-note');
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
log.error("Could not register global shortcut CTRL+ALT+P");
|
||||
await windowService.createMainWindow();
|
||||
}
|
||||
else {
|
||||
await windowService.createSetupWindow();
|
||||
}
|
||||
|
||||
await windowService.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
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* !!!!!!! This stylesheet is heavily modified compared to the original for similarity with in-editor look !!!!!!!
|
||||
* This is used for printing and tar HTML export
|
||||
*
|
||||
* CKEditor 5 (v15.0.0) content styles.
|
||||
* Generated on Wed, 27 Nov 2019 13:26:13 GMT.
|
||||
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/content-styles.html
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ck-highlight-marker-blue: #72cdfd;
|
||||
--ck-highlight-marker-green: #63f963;
|
||||
--ck-highlight-marker-pink: #fc7999;
|
||||
--ck-highlight-marker-yellow: #fdfd77;
|
||||
--ck-highlight-pen-green: #118800;
|
||||
--ck-highlight-pen-red: #e91313;
|
||||
--ck-image-style-spacing: 1.5em;
|
||||
--ck-todo-list-checkmark-size: 16px;
|
||||
font-family: Arial, Sans-Serif;
|
||||
}
|
||||
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.todo-list {
|
||||
list-style: none;
|
||||
}
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.todo-list li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.todo-list li .todo-list {
|
||||
margin-top: 5px;
|
||||
}
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.todo-list .todo-list__label > input {
|
||||
-webkit-appearance: none;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: var(--ck-todo-list-checkmark-size);
|
||||
height: var(--ck-todo-list-checkmark-size);
|
||||
vertical-align: middle;
|
||||
border: 0;
|
||||
left: -25px;
|
||||
margin-right: -15px;
|
||||
right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.todo-list .todo-list__label > input::before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid hsl(0, 0%, 20%);
|
||||
border-radius: 2px;
|
||||
transition: 250ms ease-in-out box-shadow, 250ms ease-in-out background, 250ms ease-in-out border;
|
||||
}
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.todo-list .todo-list__label > input::after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: content-box;
|
||||
pointer-events: none;
|
||||
content: '';
|
||||
left: calc( var(--ck-todo-list-checkmark-size) / 3 );
|
||||
top: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
||||
width: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
||||
height: calc( var(--ck-todo-list-checkmark-size) / 2.6 );
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.todo-list .todo-list__label > input[checked]::before {
|
||||
border-color: hsl(126, 64%, 41%);
|
||||
}
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.todo-list .todo-list__label > input[checked]::after {
|
||||
border-color: hsl(126, 64%, 41%);
|
||||
}
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.todo-list .todo-list__label .todo-list__label__description {
|
||||
vertical-align: middle;
|
||||
}
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.image {
|
||||
display: table;
|
||||
clear: both;
|
||||
text-align: center;
|
||||
margin: 1em auto;
|
||||
}
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.image > img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
min-width: 50px;
|
||||
}
|
||||
/* ckeditor5-block-quote/theme/blockquote.css */
|
||||
blockquote {
|
||||
overflow: hidden;
|
||||
padding-right: 1.5em;
|
||||
padding-left: 1.5em;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
font-style: italic;
|
||||
border-left: solid 5px hsl(0, 0%, 80%);
|
||||
}
|
||||
/* ckeditor5-block-quote/theme/blockquote.css */
|
||||
.ck-content[dir="rtl"] blockquote {
|
||||
border-left: 0;
|
||||
border-right: solid 5px hsl(0, 0%, 80%);
|
||||
}
|
||||
/* ckeditor5-image/theme/imageresize.css */
|
||||
.image.image_resized {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* ckeditor5-image/theme/imageresize.css */
|
||||
.image.image_resized img {
|
||||
width: 100%;
|
||||
}
|
||||
/* ckeditor5-image/theme/imageresize.css */
|
||||
.image.image_resized > figcaption {
|
||||
display: block;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.image-style-side,
|
||||
.image-style-align-left,
|
||||
.image-style-align-center,
|
||||
.image-style-align-right {
|
||||
max-width: 50%;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.image-style-side {
|
||||
float: right;
|
||||
margin-left: var(--ck-image-style-spacing);
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.image-style-align-left {
|
||||
float: left;
|
||||
margin-right: var(--ck-image-style-spacing);
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.image-style-align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.image-style-align-right {
|
||||
float: right;
|
||||
margin-left: var(--ck-image-style-spacing);
|
||||
}
|
||||
/* ckeditor5-media-embed/theme/mediaembed.css */
|
||||
.media {
|
||||
clear: both;
|
||||
margin: 1em 0;
|
||||
display: block;
|
||||
min-width: 15em;
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.table {
|
||||
display: table;
|
||||
margin: 0;
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.table table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
border: 1px double hsl(0, 0%, 70%);
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.table table td,
|
||||
.table table th {
|
||||
min-width: 2em;
|
||||
padding: .4em;
|
||||
border: 1px solid #d9d9d9;
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.table table th {
|
||||
font-weight: bold;
|
||||
background-color: #f5f5f5;
|
||||
text-align: left;
|
||||
}
|
||||
/* ckeditor5-highlight/theme/highlight.css */
|
||||
.marker-yellow {
|
||||
background-color: var(--ck-highlight-marker-yellow);
|
||||
}
|
||||
/* ckeditor5-highlight/theme/highlight.css */
|
||||
.marker-green {
|
||||
background-color: var(--ck-highlight-marker-green);
|
||||
}
|
||||
/* ckeditor5-highlight/theme/highlight.css */
|
||||
.marker-pink {
|
||||
background-color: var(--ck-highlight-marker-pink);
|
||||
}
|
||||
/* ckeditor5-highlight/theme/highlight.css */
|
||||
.marker-blue {
|
||||
background-color: var(--ck-highlight-marker-blue);
|
||||
}
|
||||
/* ckeditor5-highlight/theme/highlight.css */
|
||||
.pen-red {
|
||||
color: var(--ck-highlight-pen-red);
|
||||
background-color: transparent;
|
||||
}
|
||||
/* ckeditor5-highlight/theme/highlight.css */
|
||||
.pen-green {
|
||||
color: var(--ck-highlight-pen-green);
|
||||
background-color: transparent;
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.page-break {
|
||||
position: relative;
|
||||
clear: both;
|
||||
padding: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.page-break::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 2px dashed hsl(0, 0%, 77%);
|
||||
width: 100%;
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.page-break__label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: .3em .6em;
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-radius: 2px;
|
||||
font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
|
||||
font-size: 0.75em;
|
||||
font-weight: bold;
|
||||
color: hsl(0, 0%, 20%);
|
||||
background: #fff;
|
||||
box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
/* ckeditor5-basic-styles/theme/code.css */
|
||||
code {
|
||||
background-color: hsla(0, 0%, 78%, 0.3);
|
||||
padding: .15em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagecaption.css */
|
||||
.image > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: bottom;
|
||||
word-break: break-word;
|
||||
color: hsl(0, 0%, 20%);
|
||||
background-color: hsl(0, 0%, 97%);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
/* ckeditor5-horizontal-line/theme/horizontalline.css */
|
||||
hr {
|
||||
border-width: 1px 0 0;
|
||||
border-style: solid;
|
||||
border-color: hsl(0, 0%, 37%);
|
||||
margin: 0;
|
||||
}
|
||||
/* ckeditor5-code-block/theme/codeblock.css */
|
||||
pre {
|
||||
padding: 1em;
|
||||
color: #353535;
|
||||
background: hsla(0, 0%, 78%, 0.3);
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-radius: 2px;
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
tab-size: 4;
|
||||
white-space: pre-wrap;
|
||||
font-style: normal;
|
||||
min-width: 200px;
|
||||
}
|
||||
/* ckeditor5-code-block/theme/codeblock.css */
|
||||
pre code {
|
||||
background: unset;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.ck-widget__selection-handle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media print {
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.page-break {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.page-break::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
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
File diff suppressed because one or more lines are too long
Vendored
+3
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
+492
-4301
File diff suppressed because it is too large
Load Diff
+20
-27
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.37.0-beta",
|
||||
"version": "0.39.2-beta",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@@ -27,34 +27,34 @@
|
||||
"commonmark": "0.29.0",
|
||||
"cookie-parser": "1.4.4",
|
||||
"csurf": "1.10.0",
|
||||
"dayjs": "1.8.17",
|
||||
"dayjs": "1.8.18",
|
||||
"debug": "4.1.1",
|
||||
"ejs": "2.7.1",
|
||||
"ejs": "2.7.4",
|
||||
"electron-debug": "3.0.1",
|
||||
"electron-dl": "1.14.0",
|
||||
"electron-dl": "2.0.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",
|
||||
"file-type": "12.4.0",
|
||||
"file-type": "12.4.2",
|
||||
"fs-extra": "8.1.0",
|
||||
"helmet": "3.21.2",
|
||||
"html": "1.0.0",
|
||||
"html2plaintext": "2.1.2",
|
||||
"http-proxy-agent": "2.1.0",
|
||||
"https-proxy-agent": "3.0.1",
|
||||
"http-proxy-agent": "3.0.0",
|
||||
"https-proxy-agent": "4.0.0",
|
||||
"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",
|
||||
@@ -63,35 +63,28 @@
|
||||
"rimraf": "3.0.0",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"sax": "1.2.4",
|
||||
"semver": "6.3.0",
|
||||
"semver": "7.1.1",
|
||||
"serve-favicon": "2.5.0",
|
||||
"session-file-store": "1.3.1",
|
||||
"simple-node-logger": "18.12.23",
|
||||
"sqlite": "3.0.3",
|
||||
"sqlite3": "4.1.0",
|
||||
"sqlite3": "4.1.1",
|
||||
"string-similarity": "3.0.0",
|
||||
"tar-stream": "2.1.0",
|
||||
"turndown": "5.0.3",
|
||||
"turndown-plugin-gfm": "1.0.2",
|
||||
"unescape": "1.0.1",
|
||||
"ws": "7.2.0",
|
||||
"xml2js": "0.4.22"
|
||||
"ws": "7.2.1"
|
||||
},
|
||||
"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"
|
||||
"lorem-ipsum": "2.0.3"
|
||||
},
|
||||
"xo": {
|
||||
"envs": [
|
||||
"node",
|
||||
"browser"
|
||||
]
|
||||
"optionalDependencies": {
|
||||
"electron-installer-debian": "2.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+199
-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]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -728,6 +862,36 @@ class Note extends Entity {
|
||||
AND parent_notes.isDeleted = 0`, [this.noteId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise<string[][]>} - array of notePaths (each represented by array of noteIds constituting the particular note path)
|
||||
*/
|
||||
async getAllNotePaths() {
|
||||
if (this.noteId === 'root') {
|
||||
return [['root']];
|
||||
}
|
||||
|
||||
const notePaths = [];
|
||||
|
||||
for (const parentNote of await this.getParentNotes()) {
|
||||
for (const parentPath of await parentNote.getAllNotePaths()) {
|
||||
parentPath.push(this.noteId);
|
||||
notePaths.push(parentPath);
|
||||
}
|
||||
}
|
||||
|
||||
return notePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
@@ -767,7 +931,9 @@ class Note extends Entity {
|
||||
|
||||
delete pojo.isContentAvailable;
|
||||
delete pojo.__attributeCache;
|
||||
delete pojo.__ownedAttributeCache;
|
||||
delete pojo.content;
|
||||
/** zero references to contentHash, probably can be removed */
|
||||
delete pojo.contentHash;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ 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";
|
||||
import splitService from "./services/split.js";
|
||||
import optionService from "./services/options.js";
|
||||
import noteContentRenderer from "./services/note_content_renderer.js";
|
||||
|
||||
window.glob.isDesktop = utils.isDesktop;
|
||||
window.glob.isMobile = utils.isMobile;
|
||||
@@ -38,6 +42,20 @@ window.glob.isMobile = utils.isMobile;
|
||||
window.glob.getActiveNode = treeService.getActiveNode;
|
||||
window.glob.getHeaders = server.getHeaders;
|
||||
window.glob.showAddLinkDialog = () => import('./dialogs/add_link.js').then(d => d.showDialog());
|
||||
window.glob.showIncludeNoteDialog = cb => import('./dialogs/include_note.js').then(d => d.showDialog(cb));
|
||||
window.glob.loadIncludedNote = async (noteId, el) => {
|
||||
const note = await treeCache.getNote(noteId);
|
||||
|
||||
if (note) {
|
||||
$(el).empty().append($("<h3>").append(await linkService.createNoteLink(note.noteId, {
|
||||
showTooltip: false
|
||||
})));
|
||||
|
||||
const {renderedContent} = await noteContentRenderer.getRenderedContent(note);
|
||||
|
||||
$(el).append(renderedContent);
|
||||
}
|
||||
};
|
||||
// this is required by CKEditor when uploading images
|
||||
window.glob.noteChanged = noteDetailService.noteChanged;
|
||||
window.glob.refreshTree = treeService.reload;
|
||||
@@ -78,7 +96,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 +126,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 +144,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;
|
||||
}
|
||||
@@ -151,10 +159,17 @@ $noteTabContainer.on("click", ".print-note-button", async function () {
|
||||
$tabContext.$tabContent.find('.note-detail-component:visible').printThis({
|
||||
header: $("<h2>").text($tabContext.note && $tabContext.note.title).prop('outerHTML') ,
|
||||
importCSS: false,
|
||||
loadCSS: "libraries/codemirror/codemirror.css",
|
||||
loadCSS: [
|
||||
"libraries/codemirror/codemirror.css",
|
||||
"libraries/ckeditor/ckeditor-content.css"
|
||||
],
|
||||
debug: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
keyboardActionService.setGlobalActionHandler("PrintActiveNote", printActiveNote);
|
||||
|
||||
$noteTabContainer.on("click", ".print-note-button", printActiveNote);
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip({
|
||||
html: true
|
||||
@@ -181,4 +196,34 @@ noteAutocompleteService.init();
|
||||
|
||||
if (utils.isElectron()) {
|
||||
import("./services/spell_check.js").then(spellCheckService => spellCheckService.initSpellCheck());
|
||||
}
|
||||
}
|
||||
|
||||
optionService.waitForOptions().then(options => {
|
||||
if (utils.isElectron() && !options.is('nativeTitleBarVisible')) {
|
||||
$("#title-bar-buttons").show();
|
||||
|
||||
$("#minimize-btn").on('click', () => {
|
||||
$("#minimize-btn").trigger('blur');
|
||||
const {remote} = require('electron');
|
||||
remote.BrowserWindow.getFocusedWindow().minimize();
|
||||
});
|
||||
|
||||
$("#maximize-btn").on('click', () => {
|
||||
$("#maximize-btn").trigger('blur');
|
||||
const {remote} = require('electron');
|
||||
const focusedWindow = remote.BrowserWindow.getFocusedWindow();
|
||||
|
||||
if (focusedWindow.isMaximized()) {
|
||||
focusedWindow.unmaximize();
|
||||
} else {
|
||||
focusedWindow.maximize();
|
||||
}
|
||||
});
|
||||
|
||||
$("#close-btn").on('click', () => {
|
||||
$("#close-btn").trigger('blur');
|
||||
const {remote} = require('electron');
|
||||
remote.BrowserWindow.getFocusedWindow().close();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import utils from "../services/utils.js";
|
||||
import linkService from "../services/link.js";
|
||||
import libraryLoader from "../services/library_loader.js";
|
||||
import noteAutocompleteService from "../services/note_autocomplete.js";
|
||||
import treeService from "../services/tree.js";
|
||||
|
||||
const $dialog = $("#attributes-dialog");
|
||||
const $saveAttributesButton = $("#save-attributes-button");
|
||||
@@ -175,6 +176,7 @@ function AttributesModel() {
|
||||
ctx.attributes.refreshAttributes();
|
||||
|
||||
noteDetailService.reload();
|
||||
treeService.reload();
|
||||
};
|
||||
|
||||
function addLastEmptyRow() {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import server from "../services/server.js";
|
||||
import utils from "../services/utils.js";
|
||||
|
||||
const $dialog = $("#backend-log-dialog");
|
||||
const $backendLogTextArea = $("#backend-log-textarea");
|
||||
const $refreshBackendLog = $("#refresh-backend-log-button");
|
||||
|
||||
export async function showDialog() {
|
||||
utils.closeActiveDialog();
|
||||
|
||||
glob.activeDialog = $dialog;
|
||||
|
||||
$dialog.modal();
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
$backendLogTextArea.scrollTop($backendLogTextArea[0].scrollHeight);
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const backendLog = await server.get('backend-log');
|
||||
|
||||
$backendLogTextArea.text(backendLog);
|
||||
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
$refreshBackendLog.on('click', load);
|
||||
|
||||
$dialog.on('shown.bs.modal', scrollToBottom);
|
||||
@@ -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);
|
||||
|
||||
@@ -74,7 +74,7 @@ $form.on('submit', () => {
|
||||
function exportBranch(branchId, type, format, version) {
|
||||
taskId = utils.randomString(10);
|
||||
|
||||
const url = utils.getHost() + `/api/notes/${branchId}/export/${type}/${format}/${version}/${taskId}`;
|
||||
const url = utils.getUrlForDownload(`api/notes/${branchId}/export/${type}/${format}/${version}/${taskId}`);
|
||||
|
||||
utils.download(url);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import treeUtils from '../services/tree_utils.js';
|
||||
import noteAutocompleteService from '../services/note_autocomplete.js';
|
||||
import utils from "../services/utils.js";
|
||||
|
||||
const $dialog = $("#include-note-dialog");
|
||||
const $form = $("#include-note-form");
|
||||
const $autoComplete = $("#include-note-autocomplete");
|
||||
let callback = null;
|
||||
|
||||
export async function showDialog(cb) {
|
||||
callback = cb;
|
||||
|
||||
utils.closeActiveDialog();
|
||||
|
||||
glob.activeDialog = $dialog;
|
||||
|
||||
$autoComplete.val('');
|
||||
|
||||
$dialog.modal();
|
||||
|
||||
noteAutocompleteService.initNoteAutocomplete($autoComplete, { hideGoToSelectedNoteButton: true });
|
||||
noteAutocompleteService.showRecentNotes($autoComplete);
|
||||
}
|
||||
|
||||
$form.on('submit', () => {
|
||||
const notePath = $autoComplete.getSelectedPath();
|
||||
|
||||
if (notePath) {
|
||||
$dialog.modal('hide');
|
||||
|
||||
if (callback) {
|
||||
callback(treeUtils.getNoteIdFromNotePath(notePath));
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error("No noteId to include.");
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
@@ -26,6 +26,10 @@ async function convertMarkdownToHtml(text) {
|
||||
}
|
||||
|
||||
export async function importMarkdownInline() {
|
||||
if (noteDetailService.getActiveTabNoteType() !== 'text') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const {clipboard} = require('electron');
|
||||
const text = clipboard.readText();
|
||||
|
||||
@@ -102,7 +102,9 @@ async function setContentPane() {
|
||||
const $downloadButton = $('<button class="btn btn-sm btn-primary" type="button">Download</button>');
|
||||
|
||||
$downloadButton.on('click', () => {
|
||||
utils.download(utils.getHost() + `/api/notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}/download`);
|
||||
const url = utils.getUrlForDownload(`api/notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}/download`);
|
||||
|
||||
utils.download(url);
|
||||
});
|
||||
|
||||
$titleButtons.append($downloadButton);
|
||||
|
||||
@@ -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,23 @@ 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>Consistency checks</h4>
|
||||
|
||||
<button id="find-and-fix-consistency-issues-button" class="btn">Find and fix consistency issues</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 +28,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() {
|
||||
@@ -33,9 +37,8 @@ export default class AdvancedOptions {
|
||||
this.$forceFullSyncButton = $("#force-full-sync-button");
|
||||
this.$fillSyncRowsButton = $("#fill-sync-rows-button");
|
||||
this.$anonymizeButton = $("#anonymize-button");
|
||||
this.$cleanupSoftDeletedButton = $("#cleanup-soft-deleted-items-button");
|
||||
this.$cleanupUnusedImagesButton = $("#cleanup-unused-images-button");
|
||||
this.$vacuumDatabaseButton = $("#vacuum-database-button");
|
||||
this.$findAndFixConsistencyIssuesButton = $("#find-and-fix-consistency-issues-button");
|
||||
|
||||
this.$forceFullSyncButton.on('click', async () => {
|
||||
await server.post('sync/force-full-sync');
|
||||
@@ -55,26 +58,16 @@ export default class AdvancedOptions {
|
||||
toastService.showMessage("Created anonymized database");
|
||||
});
|
||||
|
||||
this.$cleanupSoftDeletedButton.on('click', async () => {
|
||||
if (confirm("Do you really want to clean up soft-deleted items?")) {
|
||||
await server.post('cleanup/cleanup-soft-deleted-items');
|
||||
|
||||
toastService.showMessage("Soft deleted items have been cleaned up");
|
||||
}
|
||||
});
|
||||
|
||||
this.$cleanupUnusedImagesButton.on('click', async () => {
|
||||
if (confirm("Do you really want to clean up unused images?")) {
|
||||
await server.post('cleanup/cleanup-unused-images');
|
||||
|
||||
toastService.showMessage("Unused images have been cleaned up");
|
||||
}
|
||||
});
|
||||
|
||||
this.$vacuumDatabaseButton.on('click', async () => {
|
||||
await server.post('cleanup/vacuum-database');
|
||||
|
||||
toastService.showMessage("Database has been vacuumed");
|
||||
});
|
||||
|
||||
this.$findAndFixConsistencyIssuesButton.on('click', async () => {
|
||||
await server.post('cleanup/find-and-fix-consistency-issues');
|
||||
|
||||
toastService.showMessage("Consistency issues should be fixed.");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,13 @@ const TPL = `
|
||||
|
||||
<input type="number" class="form-control" id="zoom-factor-select" min="0.3" max="2.0" step="0.1"/>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-4">
|
||||
<label for="one-tab-display-select">If there's only one tab, then...</label>
|
||||
<select class="form-control" id="one-tab-display-select">
|
||||
<option value="show">show the tab bar</option>
|
||||
<option value="hide">hide the tab bar</option>
|
||||
<label for="native-title-bar-select">Native title bar (requires app restart)</label>
|
||||
|
||||
<select class="form-control" id="native-title-bar-select">
|
||||
<option value="show">enabled</option>
|
||||
<option value="hide">disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,37 +70,6 @@ const TPL = `
|
||||
</div>
|
||||
|
||||
<p>Note that tree and detail font sizing is relative to the main font size setting.</p>
|
||||
|
||||
<h4>Left pane sizing</h4>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-6">
|
||||
<label for="left-pane-min-width">Left pane minimum width (in pixels)</label>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" id="left-pane-min-width" min="100" max="2000" step="1"/>
|
||||
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">px</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<label for="left-pane-min-width">Left pane width percent of window size</label>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" id="left-pane-width-percent" min="0" max="99" step="1"/>
|
||||
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Left pane width is calculated from the percent of window size, if this is smaller than minimum width, then minimum width is used. If you want to have fixed width left pane, set minimum width to the desired width and set percent to 0.</p>
|
||||
|
||||
</form>`;
|
||||
|
||||
export default class ApperanceOptions {
|
||||
@@ -108,9 +78,7 @@ export default class ApperanceOptions {
|
||||
|
||||
this.$themeSelect = $("#theme-select");
|
||||
this.$zoomFactorSelect = $("#zoom-factor-select");
|
||||
this.$oneTabDisplaySelect = $("#one-tab-display-select");
|
||||
this.$leftPaneMinWidth = $("#left-pane-min-width");
|
||||
this.$leftPaneWidthPercent = $("#left-pane-width-percent");
|
||||
this.$nativeTitleBarSelect = $("#native-title-bar-select");
|
||||
this.$mainFontSize = $("#main-font-size");
|
||||
this.$treeFontSize = $("#tree-font-size");
|
||||
this.$detailFontSize = $("#detail-font-size");
|
||||
@@ -131,7 +99,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);
|
||||
@@ -141,23 +109,10 @@ export default class ApperanceOptions {
|
||||
|
||||
this.$zoomFactorSelect.on('change', () => { zoomService.setZoomFactorAndSave(this.$zoomFactorSelect.val()); });
|
||||
|
||||
this.$oneTabDisplaySelect.on('change', () => {
|
||||
const hideTabRowForOneTab = this.$oneTabDisplaySelect.val() === 'hide' ? 'true' : 'false';
|
||||
this.$nativeTitleBarSelect.on('change', () => {
|
||||
const nativeTitleBarVisible = this.$nativeTitleBarSelect.val() === 'show' ? 'true' : 'false';
|
||||
|
||||
server.put('options/hideTabRowForOneTab/' + hideTabRowForOneTab)
|
||||
.then(optionsService.reloadOptions);
|
||||
});
|
||||
|
||||
this.$leftPaneMinWidth.on('change', async () => {
|
||||
await server.put('options/leftPaneMinWidth/' + this.$leftPaneMinWidth.val());
|
||||
|
||||
this.resizeLeftPanel();
|
||||
});
|
||||
|
||||
this.$leftPaneWidthPercent.on('change', async () => {
|
||||
await server.put('options/leftPaneWidthPercent/' + this.$leftPaneWidthPercent.val());
|
||||
|
||||
this.resizeLeftPanel();
|
||||
server.put('options/nativeTitleBarVisible/' + nativeTitleBarVisible);
|
||||
});
|
||||
|
||||
this.$mainFontSize.on('change', async () => {
|
||||
@@ -204,24 +159,13 @@ export default class ApperanceOptions {
|
||||
this.$zoomFactorSelect.prop('disabled', true);
|
||||
}
|
||||
|
||||
this.$oneTabDisplaySelect.val(options.hideTabRowForOneTab === 'true' ? 'hide' : 'show');
|
||||
|
||||
this.$leftPaneMinWidth.val(options.leftPaneMinWidth);
|
||||
this.$leftPaneWidthPercent.val(options.leftPaneWidthPercent);
|
||||
this.$nativeTitleBarSelect.val(options.nativeTitleBarVisible === 'true' ? 'show' : 'hide');
|
||||
|
||||
this.$mainFontSize.val(options.mainFontSize);
|
||||
this.$treeFontSize.val(options.treeFontSize);
|
||||
this.$detailFontSize.val(options.detailFontSize);
|
||||
}
|
||||
|
||||
resizeLeftPanel() {
|
||||
const leftPanePercent = parseInt(this.$leftPaneWidthPercent.val());
|
||||
const rightPanePercent = 100 - leftPanePercent;
|
||||
const leftPaneMinWidth = this.$leftPaneMinWidth.val();
|
||||
|
||||
this.$container.css("grid-template-columns", `minmax(${leftPaneMinWidth}px, ${leftPanePercent}fr) ${rightPanePercent}fr`);
|
||||
}
|
||||
|
||||
applyFontSizes() {
|
||||
this.$body.get(0).style.setProperty("--main-font-size", this.$mainFontSize.val() + "%");
|
||||
this.$body.get(0).style.setProperty("--tree-font-size", this.$treeFontSize.val() + "%");
|
||||
|
||||
@@ -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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,45 +3,6 @@ import server from "../../services/server.js";
|
||||
import optionsService from "../../services/options.js";
|
||||
|
||||
const TPL = `
|
||||
<h4>Show sidebar in new tab</h4>
|
||||
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="show-sidebar-in-new-tab">
|
||||
<label class="form-check-label" for="show-sidebar-in-new-tab">Show sidebar in new tab</label>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<h4>Sidebar sizing</h4>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-6">
|
||||
<label for="sidebar-min-width">Sidebar minimum width (in pixels)</label>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" id="sidebar-min-width" min="100" max="2000" step="1"/>
|
||||
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">px</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<label for="left-pane-min-width">Sidebar width percent of the detail pane</label>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" id="sidebar-width-percent" min="0" max="70" step="1"/>
|
||||
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Sidebar width is calculated from the percent of the detail pane, if this is smaller than minimum width, then minimum width is used. If you want to have fixed width sidebar, set minimum width to the desired width and set percent to 0.</p>
|
||||
|
||||
<h4>Widgets</h4>
|
||||
|
||||
<div id="widgets-configuration" class="row">
|
||||
@@ -58,48 +19,15 @@ export default class SidebarOptions {
|
||||
constructor() {
|
||||
$("#options-sidebar").html(TPL);
|
||||
|
||||
this.$sidebarMinWidth = $("#sidebar-min-width");
|
||||
this.$sidebarWidthPercent = $("#sidebar-width-percent");
|
||||
this.$showSidebarInNewTab = $("#show-sidebar-in-new-tab");
|
||||
this.$widgetsConfiguration = $("#widgets-configuration");
|
||||
this.$widgetsEnabled = $("#widgets-enabled");
|
||||
this.$widgetsDisabled = $("#widgets-disabled");
|
||||
|
||||
this.$sidebarMinWidth.on('change', async () => {
|
||||
await server.put('options/sidebarMinWidth/' + this.$sidebarMinWidth.val());
|
||||
|
||||
this.resizeSidebar();
|
||||
});
|
||||
|
||||
this.$sidebarWidthPercent.on('change', async () => {
|
||||
await server.put('options/sidebarWidthPercent/' + this.$sidebarWidthPercent.val());
|
||||
|
||||
this.resizeSidebar();
|
||||
});
|
||||
|
||||
this.$showSidebarInNewTab.on('change', async () => {
|
||||
const flag = this.$showSidebarInNewTab.is(":checked") ? 'true' : 'false';
|
||||
|
||||
await server.put('options/showSidebarInNewTab/' + flag);
|
||||
|
||||
optionsService.reloadOptions();
|
||||
});
|
||||
}
|
||||
|
||||
async optionsLoaded(options) {
|
||||
this.$widgetsEnabled.empty();
|
||||
this.$widgetsDisabled.empty();
|
||||
|
||||
this.$sidebarMinWidth.val(options.sidebarMinWidth);
|
||||
this.$sidebarWidthPercent.val(options.sidebarWidthPercent);
|
||||
|
||||
if (parseInt(options.showSidebarInNewTab)) {
|
||||
this.$showSidebarInNewTab.attr("checked", "checked");
|
||||
}
|
||||
else {
|
||||
this.$showSidebarInNewTab.removeAttr("checked");
|
||||
}
|
||||
|
||||
const widgets = [
|
||||
{name: 'attributes', title: 'Attributes'},
|
||||
{name: 'linkMap', title: 'Link map'},
|
||||
@@ -188,19 +116,4 @@ export default class SidebarOptions {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
resizeSidebar() {
|
||||
const sidebarWidthPercent = parseInt(this.$sidebarWidthPercent.val());
|
||||
const sidebarMinWidth = this.$sidebarMinWidth.val();
|
||||
|
||||
// need to find them dynamically since they change
|
||||
const $sidebar = $(".note-detail-sidebar");
|
||||
|
||||
const $content = $(".note-detail-content");
|
||||
|
||||
$sidebar.css("width", sidebarWidthPercent + '%');
|
||||
$sidebar.css("min-width", sidebarMinWidth + 'px');
|
||||
|
||||
$content.css("width", (100 - sidebarWidthPercent) + '%');
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -44,7 +44,10 @@ export async function showDialog() {
|
||||
const note = await treeCache.getNote(change.noteId);
|
||||
const notePath = await treeService.getSomeNotePath(note);
|
||||
|
||||
noteLink = await linkService.createNoteLinkWithPath(notePath, change.title);
|
||||
noteLink = await linkService.createNoteLink(notePath, {
|
||||
title: change.title,
|
||||
showNotePath: true
|
||||
});
|
||||
}
|
||||
|
||||
changesListEl.append($('<li>')
|
||||
|
||||
@@ -6,9 +6,8 @@ import utils from "../services/utils.js";
|
||||
const $dialog = $("#sql-console-dialog");
|
||||
const $query = $('#sql-console-query');
|
||||
const $executeButton = $('#sql-console-execute');
|
||||
const $resultHead = $('#sql-console-results thead');
|
||||
const $resultBody = $('#sql-console-results tbody');
|
||||
const $tables = $("#sql-console-tables");
|
||||
const $tableSchemas = $("#sql-console-table-schemas");
|
||||
const $resultContainer = $("#result-container");
|
||||
|
||||
let codeEditor;
|
||||
|
||||
@@ -19,7 +18,7 @@ export async function showDialog() {
|
||||
|
||||
glob.activeDialog = $dialog;
|
||||
|
||||
await showTables();
|
||||
await showTableSchemas();
|
||||
|
||||
$dialog.modal();
|
||||
}
|
||||
@@ -45,6 +44,10 @@ async function initEditor() {
|
||||
|
||||
codeEditor.setOption("mode", "text/x-sqlite");
|
||||
CodeMirror.autoLoadMode(codeEditor, "sql");
|
||||
|
||||
codeEditor.setValue(`SELECT title, isProtected, type, mime FROM notes WHERE noteId = 'root';
|
||||
---
|
||||
SELECT noteId, parentNoteId, notePosition, prefix FROM branches WHERE branchId = 'root';`);
|
||||
}
|
||||
|
||||
codeEditor.focus();
|
||||
@@ -70,55 +73,66 @@ async function execute() {
|
||||
toastService.showMessage("Query was executed successfully.");
|
||||
}
|
||||
|
||||
const rows = result.rows;
|
||||
const results = result.results;
|
||||
|
||||
$resultHead.empty();
|
||||
$resultBody.empty();
|
||||
$resultContainer.empty();
|
||||
|
||||
for (const rows of results) {
|
||||
if (rows.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const $table = $('<table class="table table-striped">');
|
||||
$resultContainer.append($table);
|
||||
|
||||
if (rows.length > 0) {
|
||||
const result = rows[0];
|
||||
const rowEl = $("<tr>");
|
||||
const $row = $("<tr>");
|
||||
|
||||
for (const key in result) {
|
||||
rowEl.append($("<th>").html(key));
|
||||
$row.append($("<th>").html(key));
|
||||
}
|
||||
|
||||
$resultHead.append(rowEl);
|
||||
}
|
||||
$table.append($row);
|
||||
|
||||
for (const result of rows) {
|
||||
const rowEl = $("<tr>");
|
||||
for (const result of rows) {
|
||||
const $row = $("<tr>");
|
||||
|
||||
for (const key in result) {
|
||||
rowEl.append($("<td>").html(result[key]));
|
||||
for (const key in result) {
|
||||
$row.append($("<td>").html(result[key]));
|
||||
}
|
||||
|
||||
$table.append($row);
|
||||
}
|
||||
|
||||
$resultBody.append(rowEl);
|
||||
}
|
||||
}
|
||||
|
||||
async function showTables() {
|
||||
async function showTableSchemas() {
|
||||
const tables = await server.get('sql/schema');
|
||||
|
||||
$tables.empty();
|
||||
$tableSchemas.empty();
|
||||
|
||||
for (const table of tables) {
|
||||
const $tableLink = $('<button class="btn">').text(table.name);
|
||||
|
||||
const $columns = $("<table>");
|
||||
const $columns = $("<ul>");
|
||||
|
||||
for (const column of table.columns) {
|
||||
$columns.append(
|
||||
$("<tr>")
|
||||
.append($("<td>").text(column.name))
|
||||
.append($("<td>").text(column.type))
|
||||
$("<li>")
|
||||
.append($("<span>").text(column.name))
|
||||
.append($("<span>").text(column.type))
|
||||
);
|
||||
}
|
||||
|
||||
$tables.append($tableLink).append(" ");
|
||||
$tableSchemas.append($tableLink).append(" ");
|
||||
|
||||
$tableLink
|
||||
.tooltip({html: true, title: $columns.html()})
|
||||
.tooltip({
|
||||
html: true,
|
||||
placement: 'bottom',
|
||||
boundary: 'window',
|
||||
title: $columns[0].outerHTML
|
||||
})
|
||||
.on('click', () => codeEditor.setValue("SELECT * FROM " + table.name + " LIMIT 100"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ class Branch {
|
||||
this.branchId = row.branchId;
|
||||
/** @param {string} */
|
||||
this.noteId = row.noteId;
|
||||
this.note = null;
|
||||
/** @param {string} */
|
||||
this.parentNoteId = row.parentNoteId;
|
||||
/** @param {int} */
|
||||
@@ -19,7 +18,7 @@ class Branch {
|
||||
|
||||
/** @returns {NoteShort} */
|
||||
async getNote() {
|
||||
return await this.treeCache.getNote(this.noteId);
|
||||
return this.treeCache.getNote(this.noteId);
|
||||
}
|
||||
|
||||
/** @returns {boolean} true if it's top level, meaning its parent is root note */
|
||||
|
||||
@@ -35,6 +35,8 @@ class NoteShort {
|
||||
this.archived = row.archived;
|
||||
/** @param {string} */
|
||||
this.cssClass = row.cssClass;
|
||||
/** @param {string} */
|
||||
this.iconClass = row.iconClass;
|
||||
|
||||
/** @type {string[]} */
|
||||
this.parents = [];
|
||||
@@ -152,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -297,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,7 +328,7 @@ class NoteShort {
|
||||
const dto = Object.assign({}, this);
|
||||
delete dto.treeCache;
|
||||
delete dto.archived;
|
||||
delete dto.attributeCache;
|
||||
delete dto.__attributeCache;
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -4,83 +4,97 @@ import cloningService from "./cloning.js";
|
||||
import toastService from "./toast.js";
|
||||
import hoistedNoteService from "./hoisted_note.js";
|
||||
|
||||
let clipboardIds = [];
|
||||
/*
|
||||
* Clipboard contains node keys which are not stable. If a (part of the) tree is reloaded,
|
||||
* node keys in the clipboard might not exist anymore. Code here should then be ready to deal
|
||||
* with this.
|
||||
*/
|
||||
|
||||
let clipboardNodeKeys = [];
|
||||
let clipboardMode = null;
|
||||
|
||||
async function pasteAfter(node) {
|
||||
async function pasteAfter(afterNode) {
|
||||
if (isClipboardEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (clipboardMode === 'cut') {
|
||||
const nodes = clipboardIds.map(nodeKey => treeUtils.getNodeByKey(nodeKey));
|
||||
const nodes = clipboardNodeKeys.map(nodeKey => treeUtils.getNodeByKey(nodeKey));
|
||||
|
||||
await treeChangesService.moveAfterNode(nodes, node);
|
||||
await treeChangesService.moveAfterNode(nodes, afterNode);
|
||||
|
||||
clipboardIds = [];
|
||||
clipboardNodeKeys = [];
|
||||
clipboardMode = null;
|
||||
}
|
||||
else if (clipboardMode === 'copy') {
|
||||
for (const noteId of clipboardIds) {
|
||||
await cloningService.cloneNoteAfter(noteId, node.data.branchId);
|
||||
for (const nodeKey of clipboardNodeKeys) {
|
||||
const clipNode = treeUtils.getNodeByKey(nodeKey);
|
||||
|
||||
await cloningService.cloneNoteAfter(clipNode.data.noteId, afterNode.data.branchId);
|
||||
}
|
||||
|
||||
// copy will keep clipboardIds and clipboardMode so it's possible to paste into multiple places
|
||||
}
|
||||
else if (clipboardIds.length === 0) {
|
||||
// just do nothing
|
||||
}
|
||||
else {
|
||||
toastService.throwError("Unrecognized clipboard mode=" + clipboardMode);
|
||||
}
|
||||
}
|
||||
|
||||
async function pasteInto(node) {
|
||||
async function pasteInto(parentNode) {
|
||||
if (isClipboardEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (clipboardMode === 'cut') {
|
||||
const nodes = clipboardIds.map(nodeKey => treeUtils.getNodeByKey(nodeKey));
|
||||
const nodes = clipboardNodeKeys.map(nodeKey => treeUtils.getNodeByKey(nodeKey));
|
||||
|
||||
await treeChangesService.moveToNode(nodes, node);
|
||||
await treeChangesService.moveToNode(nodes, parentNode);
|
||||
|
||||
await node.setExpanded(true);
|
||||
await parentNode.setExpanded(true);
|
||||
|
||||
clipboardIds = [];
|
||||
clipboardNodeKeys = [];
|
||||
clipboardMode = null;
|
||||
}
|
||||
else if (clipboardMode === 'copy') {
|
||||
for (const noteId of clipboardIds) {
|
||||
await cloningService.cloneNoteTo(noteId, node.data.noteId);
|
||||
for (const nodeKey of clipboardNodeKeys) {
|
||||
const clipNode = treeUtils.getNodeByKey(nodeKey);
|
||||
|
||||
await cloningService.cloneNoteTo(clipNode.data.noteId, parentNode.data.noteId);
|
||||
}
|
||||
|
||||
await node.setExpanded(true);
|
||||
await parentNode.setExpanded(true);
|
||||
|
||||
// copy will keep clipboardIds and clipboardMode so it's possible to paste into multiple places
|
||||
}
|
||||
else if (clipboardIds.length === 0) {
|
||||
// just do nothing
|
||||
}
|
||||
else {
|
||||
toastService.throwError("Unrecognized clipboard mode=" + mode);
|
||||
}
|
||||
}
|
||||
|
||||
function copy(nodes) {
|
||||
clipboardIds = nodes.map(node => node.data.noteId);
|
||||
clipboardNodeKeys = nodes.map(node => node.key);
|
||||
clipboardMode = 'copy';
|
||||
|
||||
toastService.showMessage("Note(s) have been copied into clipboard.");
|
||||
}
|
||||
|
||||
function cut(nodes) {
|
||||
clipboardIds = nodes
|
||||
clipboardNodeKeys = nodes
|
||||
.filter(node => node.data.noteId !== hoistedNoteService.getHoistedNoteNoPromise())
|
||||
.filter(node => node.getParent().data.noteType !== 'search')
|
||||
.map(node => node.data.noteId);
|
||||
.map(node => node.key);
|
||||
|
||||
if (clipboardIds.length > 0) {
|
||||
if (clipboardNodeKeys.length > 0) {
|
||||
clipboardMode = 'cut';
|
||||
|
||||
toastService.showMessage("Note(s) have been cut into clipboard.");
|
||||
}
|
||||
}
|
||||
|
||||
function isEmpty() {
|
||||
return clipboardIds.length === 0;
|
||||
function isClipboardEmpty() {
|
||||
clipboardNodeKeys = clipboardNodeKeys.filter(key => !!treeUtils.getNodeByKey(key));
|
||||
|
||||
return clipboardNodeKeys.length === 0;
|
||||
}
|
||||
|
||||
export default {
|
||||
@@ -88,5 +102,5 @@ export default {
|
||||
pasteInto,
|
||||
cut,
|
||||
copy,
|
||||
isEmpty
|
||||
isClipboardEmpty
|
||||
}
|
||||
@@ -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,12 @@ 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";
|
||||
import server from "./server.js";
|
||||
|
||||
const NOTE_REVISIONS = "../dialogs/note_revisions.js";
|
||||
const OPTIONS = "../dialogs/options.js";
|
||||
@@ -12,6 +18,7 @@ const JUMP_TO_NOTE = "../dialogs/jump_to_note.js";
|
||||
const NOTE_SOURCE = "../dialogs/note_source.js";
|
||||
const RECENT_CHANGES = "../dialogs/recent_changes.js";
|
||||
const SQL_CONSOLE = "../dialogs/sql_console.js";
|
||||
const BACKEND_LOG = "../dialogs/backend_log.js";
|
||||
const ATTRIBUTES = "../dialogs/attributes.js";
|
||||
const HELP = "../dialogs/help.js";
|
||||
const NOTE_INFO = "../dialogs/note_info.js";
|
||||
@@ -26,92 +33,123 @@ 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);
|
||||
|
||||
const showBackendLogDialog = () => import(BACKEND_LOG).then(d => d.showDialog());
|
||||
$("#show-backend-log-button").on('click', showBackendLogDialog);
|
||||
keyboardActionService.setGlobalActionHandler("ShowBackendLog", showBackendLogDialog);
|
||||
|
||||
$("#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");
|
||||
});
|
||||
let zenModeActive = false;
|
||||
|
||||
// hide (toggle) everything except for the note content for distraction free writing
|
||||
utils.bindGlobalShortcut('alt+t', e => {
|
||||
// hide (toggle) everything except for the note content for zen mode
|
||||
const toggleZenMode = () => {
|
||||
if (!zenModeActive) {
|
||||
$(".hide-in-zen-mode,.gutter").addClass("hidden-by-zen-mode");
|
||||
$("#container").addClass("zen-mode");
|
||||
zenModeActive = true;
|
||||
}
|
||||
else {
|
||||
// not hiding / showing explicitly since element might be hidden also for other reasons
|
||||
$(".hide-in-zen-mode,.gutter").removeClass("hidden-by-zen-mode");
|
||||
$("#container").removeClass("zen-mode");
|
||||
zenModeActive = false;
|
||||
}
|
||||
};
|
||||
|
||||
$("#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());
|
||||
|
||||
keyboardActionService.setGlobalActionHandler("PasteMarkdownIntoText", async () => {
|
||||
const dialog = await import("../dialogs/markdown_import.js");
|
||||
|
||||
dialog.importMarkdownInline();
|
||||
});
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const openDevTools = () => {
|
||||
require('electron').remote.getCurrentWindow().toggleDevTools();
|
||||
@@ -119,8 +157,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 +179,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 +200,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 +208,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 +224,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 +234,60 @@ 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 {note} = await server.post(`notes/${todayNote.noteId}/children?target=into`, {
|
||||
title: 'new note',
|
||||
content: '',
|
||||
type: 'text',
|
||||
isProtected: todayNote.isProtected
|
||||
});
|
||||
|
||||
await treeService.expandToNote(note.noteId);
|
||||
|
||||
await noteDetailService.openInTab(note.noteId, true);
|
||||
|
||||
noteDetailService.focusAndSelectTitle();
|
||||
});
|
||||
|
||||
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 {
|
||||
|
||||
@@ -125,14 +125,14 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes given anonymous function on the server.
|
||||
* Executes given anonymous function on the backend.
|
||||
* Internally this serializes the anonymous function into string and sends it to backend via AJAX.
|
||||
*
|
||||
* @param {string} script - script to be executed on the backend
|
||||
* @param {Array.<?>} params - list of parameters to the anonymous function to be send to backend
|
||||
* @return {Promise<*>} return value of the executed function on the backend
|
||||
*/
|
||||
this.runOnServer = async (script, params = []) => {
|
||||
this.runOnBackend = async (script, params = []) => {
|
||||
if (typeof script === "function") {
|
||||
script = script.toString();
|
||||
}
|
||||
@@ -159,6 +159,12 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated new name of this API call is runOnBackend so use that
|
||||
* @method
|
||||
*/
|
||||
this.runOnServer = this.runOnBackend;
|
||||
|
||||
/**
|
||||
* This is a powerful search method - you can search by attributes and their values, e.g.:
|
||||
* "@dateModified =* MONTH AND @log". See full documentation for all options at: https://github.com/zadam/trilium/wiki/Search
|
||||
@@ -382,6 +388,17 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
|
||||
* @param {function} handler
|
||||
*/
|
||||
this.bindGlobalShortcut = utils.bindGlobalShortcut;
|
||||
|
||||
/**
|
||||
* Trilium runs in backend and frontend process, when something is changed on the backend from script,
|
||||
* frontend will get asynchronously synchronized.
|
||||
*
|
||||
* This method returns a promise which resolves once all the backend -> frontend synchronization is finished.
|
||||
* Typical use case is when new note has been created, we should wait until it is synced into frontend and only then activate it.
|
||||
*
|
||||
* @method
|
||||
*/
|
||||
this.waitUntilSynced = ws.waitForMaxKnownSyncId;
|
||||
}
|
||||
|
||||
export default FrontendScriptApi;
|
||||
@@ -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
|
||||
};
|
||||
@@ -6,15 +6,14 @@ import noteDetailService from "./note_detail.js";
|
||||
function getNotePathFromUrl(url) {
|
||||
const notePathMatch = /#(root[A-Za-z0-9/]*)$/.exec(url);
|
||||
|
||||
if (notePathMatch === null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return notePathMatch[1];
|
||||
}
|
||||
return notePathMatch === null ? null : notePathMatch[1];
|
||||
}
|
||||
|
||||
async function createNoteLink(notePath, noteTitle = null, tooltip = true) {
|
||||
async function createNoteLink(notePath, options = {}) {
|
||||
let noteTitle = options.title;
|
||||
const showTooltip = options.showTooltip === undefined ? true : options.showTooltip;
|
||||
const showNotePath = options.showNotePath === undefined ? false : options.showNotePath;
|
||||
|
||||
if (!noteTitle) {
|
||||
const {noteId, parentNoteId} = treeUtils.getNoteIdAndParentIdFromNotePath(notePath);
|
||||
|
||||
@@ -27,30 +26,26 @@ async function createNoteLink(notePath, noteTitle = null, tooltip = true) {
|
||||
}).attr('data-action', 'note')
|
||||
.attr('data-note-path', notePath);
|
||||
|
||||
if (!tooltip) {
|
||||
if (!showTooltip) {
|
||||
$noteLink.addClass("no-tooltip-preview");
|
||||
}
|
||||
|
||||
return $noteLink;
|
||||
}
|
||||
const $container = $("<span>").append($noteLink);
|
||||
|
||||
async function createNoteLinkWithPath(notePath, noteTitle = null) {
|
||||
const $link = await createNoteLink(notePath, noteTitle);
|
||||
if (showNotePath) {
|
||||
notePath = await treeService.resolveNotePath(notePath);
|
||||
|
||||
const $res = $("<span>").append($link);
|
||||
|
||||
if (notePath.includes("/")) {
|
||||
const noteIds = notePath.split("/");
|
||||
noteIds.pop(); // remove last element
|
||||
|
||||
const parentNotePath = noteIds.join("/").trim();
|
||||
|
||||
if (parentNotePath) {
|
||||
$res.append($("<small>").text(" (" + await treeUtils.getNotePathTitle(parentNotePath) + ")"));
|
||||
$container.append($("<small>").text(" (" + await treeUtils.getNotePathTitle(parentNotePath) + ")"));
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
return $container;
|
||||
}
|
||||
|
||||
function getNotePathFromLink($link) {
|
||||
@@ -66,13 +61,16 @@ function getNotePathFromLink($link) {
|
||||
}
|
||||
|
||||
function goToLink(e) {
|
||||
const $link = $(e.target);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const $link = $(e.target).closest("a");
|
||||
|
||||
const notePath = getNotePathFromLink($link);
|
||||
|
||||
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);
|
||||
@@ -89,9 +87,6 @@ function goToLink(e) {
|
||||
}
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -118,7 +113,7 @@ function addTextToEditor(text) {
|
||||
}
|
||||
|
||||
function newTabContextMenu(e) {
|
||||
const $link = $(e.target);
|
||||
const $link = $(e.target).closest("a");
|
||||
|
||||
const notePath = getNotePathFromLink($link);
|
||||
|
||||
@@ -142,22 +137,27 @@ function newTabContextMenu(e) {
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on('contextmenu', '.note-detail-text a', newTabContextMenu);
|
||||
$(document).on('contextmenu', "a[data-action='note']", newTabContextMenu);
|
||||
$(document).on('contextmenu', ".note-detail-render a", newTabContextMenu);
|
||||
|
||||
// when click on link popup, in case of internal link, just go the the referenced note instead of default behavior
|
||||
// of opening the link in new window/tab
|
||||
$(document).on('mousedown', "a[data-action='note']", goToLink);
|
||||
$(document).on('mousedown', 'div.popover-content a, div.ui-tooltip-content a', goToLink);
|
||||
$(document).on('dblclick', '.note-detail-text a', goToLink);
|
||||
$(document).on('mousedown', '.note-detail-text a', function (e) {
|
||||
const notePath = getNotePathFromLink($(e.target));
|
||||
if (notePath && ((e.which === 1 && e.ctrlKey) || e.which === 2)) {
|
||||
const $link = $(e.target).closest("a");
|
||||
const notePath = getNotePathFromLink($link);
|
||||
|
||||
if ((e.which === 1 && e.ctrlKey) || e.which === 2) {
|
||||
// if it's a ctrl-click, then we open on new tab, otherwise normal flow (CKEditor opens link-editing dialog)
|
||||
e.preventDefault();
|
||||
|
||||
noteDetailService.loadNoteDetail(notePath, { newTab: true });
|
||||
if (notePath) {
|
||||
noteDetailService.loadNoteDetail(notePath, {newTab: true});
|
||||
}
|
||||
else {
|
||||
const address = $link.attr('href');
|
||||
|
||||
window.open(address, '_blank');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -166,24 +166,20 @@ $(document).on('mousedown', '.note-detail-text a', function (e) {
|
||||
$(document).on('mousedown', '.note-detail-book a', goToLink);
|
||||
$(document).on('mousedown', '.note-detail-render a', goToLink);
|
||||
$(document).on('mousedown', '.note-detail-text.ck-read-only a', goToLink);
|
||||
$(document).on('mousedown', 'span.ck-button__label', e => {
|
||||
// this is a link preview dialog from CKEditor link editing
|
||||
// for some reason clicked element is span
|
||||
|
||||
const url = $(e.target).text();
|
||||
const notePath = getNotePathFromUrl(url);
|
||||
|
||||
if (notePath) {
|
||||
treeService.activateNote(notePath);
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
$(document).on('mousedown', 'a.ck-link-actions__preview', goToLink);
|
||||
$(document).on('click', 'a.ck-link-actions__preview', e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
$(document).on('contextmenu', 'a.ck-link-actions__preview', newTabContextMenu);
|
||||
$(document).on('contextmenu', '.note-detail-text a', newTabContextMenu);
|
||||
$(document).on('contextmenu', "a[data-action='note']", newTabContextMenu);
|
||||
$(document).on('contextmenu', ".note-detail-render a", newTabContextMenu);
|
||||
|
||||
export default {
|
||||
getNotePathFromUrl,
|
||||
createNoteLink,
|
||||
createNoteLinkWithPath,
|
||||
addLinkToEditor,
|
||||
addTextToEditor,
|
||||
goToLink
|
||||
|
||||
@@ -88,7 +88,7 @@ export default class LinkMap {
|
||||
.addClass("note-box")
|
||||
.prop("id", noteBoxId);
|
||||
|
||||
linkService.createNoteLink(noteId, note.title).then($link => {
|
||||
linkService.createNoteLink(noteId, {title: note.title}).then($link => {
|
||||
$link.on('click', e => {
|
||||
try {
|
||||
$link.tooltip('dispose');
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import server from "./server.js";
|
||||
import utils from "./utils.js";
|
||||
import renderService from "./render.js";
|
||||
import protectedSessionService from "./protected_session.js";
|
||||
import protectedSessionHolder from "./protected_session_holder.js";
|
||||
|
||||
async function getRenderedContent(note) {
|
||||
const type = getRenderingType(note);
|
||||
|
||||
let rendered;
|
||||
|
||||
if (type === 'text') {
|
||||
const fullNote = await server.get('notes/' + note.noteId);
|
||||
|
||||
const $content = $("<div>").html(fullNote.content);
|
||||
|
||||
if (utils.isHtmlEmpty(fullNote.content)) {
|
||||
rendered = "";
|
||||
}
|
||||
else {
|
||||
rendered = $content;
|
||||
}
|
||||
}
|
||||
else if (type === 'code') {
|
||||
const fullNote = await server.get('notes/' + note.noteId);
|
||||
|
||||
if (fullNote.content.trim() === "") {
|
||||
rendered = "";
|
||||
}
|
||||
|
||||
rendered = $("<pre>").text(fullNote.content);
|
||||
}
|
||||
else if (type === 'image') {
|
||||
rendered = $("<img>").attr("src", `api/images/${note.noteId}/${note.title}`);
|
||||
}
|
||||
else if (type === 'file') {
|
||||
function getFileUrl() {
|
||||
return utils.getUrlForDownload("api/notes/" + note.noteId + "/download");
|
||||
}
|
||||
|
||||
const $downloadButton = $('<button class="file-download btn btn-primary" type="button">Download</button>');
|
||||
const $openButton = $('<button class="file-open btn btn-primary" type="button">Open</button>');
|
||||
|
||||
$downloadButton.on('click', () => utils.download(getFileUrl()));
|
||||
$openButton.on('click', () => {
|
||||
if (utils.isElectron()) {
|
||||
const open = require("open");
|
||||
|
||||
open(getFileUrl(), {url: true});
|
||||
}
|
||||
else {
|
||||
window.location.href = getFileUrl();
|
||||
}
|
||||
});
|
||||
|
||||
// open doesn't work for protected notes since it works through browser which isn't in protected session
|
||||
$openButton.toggle(!note.isProtected);
|
||||
|
||||
rendered = $('<div>')
|
||||
.append($downloadButton)
|
||||
.append(' ')
|
||||
.append($openButton);
|
||||
}
|
||||
else if (type === 'render') {
|
||||
const $el = $('<div>');
|
||||
|
||||
await renderService.render(note, $el, this.ctx);
|
||||
|
||||
rendered = $el;
|
||||
}
|
||||
else if (type === 'protected-session') {
|
||||
const $button = $(`<button class="btn btn-sm"><span class="bx bx-log-in"></span> Enter protected session</button>`)
|
||||
.on('click', protectedSessionService.enterProtectedSession);
|
||||
|
||||
rendered = $("<div>")
|
||||
.append("<div>This note is protected and to access it you need to enter password.</div>")
|
||||
.append("<br/>")
|
||||
.append($button);
|
||||
}
|
||||
else {
|
||||
rendered = "<em>Content of this note cannot be displayed in the book format</em>";
|
||||
}
|
||||
|
||||
return {
|
||||
renderedContent: rendered,
|
||||
type
|
||||
};
|
||||
}
|
||||
|
||||
function getRenderingType(note) {
|
||||
let type = note.type;
|
||||
|
||||
if (note.isProtected) {
|
||||
if (protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||
protectedSessionHolder.touchProtectedSession();
|
||||
}
|
||||
else {
|
||||
type = 'protected-session';
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
export default {
|
||||
getRenderedContent
|
||||
};
|
||||
@@ -4,11 +4,11 @@ import server from './server.js';
|
||||
import ws from "./ws.js";
|
||||
import treeCache from "./tree_cache.js";
|
||||
import NoteFull from "../entities/note_full.js";
|
||||
import bundleService from "./bundle.js";
|
||||
import utils from "./utils.js";
|
||||
import contextMenuService from "./context_menu.js";
|
||||
import treeUtils from "./tree_utils.js";
|
||||
import tabRow from "./tab_row.js";
|
||||
import keyboardActionService from "./keyboard_actions.js";
|
||||
|
||||
const $tabContentsContainer = $("#note-tab-container");
|
||||
const $savedIndicator = $(".saved-indicator");
|
||||
@@ -35,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) {
|
||||
@@ -274,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]);
|
||||
}
|
||||
}
|
||||
@@ -420,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);
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import server from "./server.js";
|
||||
import linkService from "./link.js";
|
||||
import utils from "./utils.js";
|
||||
import treeCache from "./tree_cache.js";
|
||||
import renderService from "./render.js";
|
||||
import protectedSessionHolder from "./protected_session_holder.js";
|
||||
import protectedSessionService from "./protected_session.js";
|
||||
import noteContentRenderer from "./note_content_renderer.js";
|
||||
|
||||
const MIN_ZOOM_LEVEL = 1;
|
||||
const MAX_ZOOM_LEVEL = 6;
|
||||
@@ -129,18 +125,18 @@ class NoteDetailBook {
|
||||
|
||||
async renderIntoElement(note, $container) {
|
||||
for (const childNote of await note.getChildNotes()) {
|
||||
const type = this.getRenderingType(childNote);
|
||||
|
||||
const childNotePath = this.ctx.notePath + '/' + childNote.noteId;
|
||||
|
||||
const {type, renderedContent} = await noteContentRenderer.getRenderedContent(childNote);
|
||||
|
||||
const $card = $('<div class="note-book-card">')
|
||||
.attr('data-note-id', childNote.noteId)
|
||||
.css("flex-basis", ZOOMS[this.zoomLevel].width)
|
||||
.addClass("type-" + type)
|
||||
.append($('<h5 class="note-book-title">').append(await linkService.createNoteLink(childNotePath, null, false)))
|
||||
.append($('<h5 class="note-book-title">').append(await linkService.createNoteLink(childNotePath, {showTooltip: false})))
|
||||
.append($('<div class="note-book-content">')
|
||||
.css("max-height", ZOOMS[this.zoomLevel].height)
|
||||
.append(await this.getNoteContent(type, childNote)));
|
||||
.append(renderedContent));
|
||||
|
||||
const childCount = childNote.getChildNoteIds().length;
|
||||
|
||||
@@ -158,81 +154,6 @@ class NoteDetailBook {
|
||||
}
|
||||
}
|
||||
|
||||
async getNoteContent(type, note) {
|
||||
if (type === 'text') {
|
||||
const fullNote = await server.get('notes/' + note.noteId);
|
||||
|
||||
const $content = $("<div>").html(fullNote.content);
|
||||
|
||||
if (utils.isHtmlEmpty(fullNote.content)) {
|
||||
return "";
|
||||
}
|
||||
else {
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
else if (type === 'code') {
|
||||
const fullNote = await server.get('notes/' + note.noteId);
|
||||
|
||||
if (fullNote.content.trim() === "") {
|
||||
return "";
|
||||
}
|
||||
|
||||
return $("<pre>").text(fullNote.content);
|
||||
}
|
||||
else if (type === 'image') {
|
||||
return $("<img>").attr("src", `api/images/${note.noteId}/${note.title}`);
|
||||
}
|
||||
else if (type === 'file') {
|
||||
function getFileUrl() {
|
||||
// electron needs absolute URL so we extract current host, port, protocol
|
||||
return utils.getHost() + "/api/notes/" + note.noteId + "/download";
|
||||
}
|
||||
|
||||
const $downloadButton = $('<button class="file-download btn btn-primary" type="button">Download</button>');
|
||||
const $openButton = $('<button class="file-open btn btn-primary" type="button">Open</button>');
|
||||
|
||||
$downloadButton.on('click', () => utils.download(getFileUrl()));
|
||||
$openButton.on('click', () => {
|
||||
if (utils.isElectron()) {
|
||||
const open = require("open");
|
||||
|
||||
open(getFileUrl(), {url: true});
|
||||
}
|
||||
else {
|
||||
window.location.href = getFileUrl();
|
||||
}
|
||||
});
|
||||
|
||||
// open doesn't work for protected notes since it works through browser which isn't in protected session
|
||||
$openButton.toggle(!note.isProtected);
|
||||
|
||||
return $('<div>')
|
||||
.append($downloadButton)
|
||||
.append(' ')
|
||||
.append($openButton);
|
||||
}
|
||||
else if (type === 'render') {
|
||||
const $el = $('<div>');
|
||||
|
||||
await renderService.render(note, $el, this.ctx);
|
||||
|
||||
return $el;
|
||||
}
|
||||
else if (type === 'protected-session') {
|
||||
const $button = $(`<button class="btn btn-sm"><span class="bx bx-log-in"></span> Enter protected session</button>`)
|
||||
.on('click', protectedSessionService.enterProtectedSession);
|
||||
|
||||
return $("<div>")
|
||||
.append("<div>This note is protected and to access it you need to enter password.</div>")
|
||||
.append("<br/>")
|
||||
.append($button);
|
||||
}
|
||||
else {
|
||||
return "<em>Content of this note cannot be displayed in the book format</em>";
|
||||
}
|
||||
}
|
||||
|
||||
/** @return {boolean} true if this is "auto book" activated (empty text note) and not explicit book note */
|
||||
isAutoBook() {
|
||||
return this.ctx.note.type !== 'book';
|
||||
@@ -257,21 +178,6 @@ class NoteDetailBook {
|
||||
}
|
||||
}
|
||||
|
||||
getRenderingType(childNote) {
|
||||
let type = childNote.type;
|
||||
|
||||
if (childNote.isProtected) {
|
||||
if (protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||
protectedSessionHolder.touchProtectedSession();
|
||||
}
|
||||
else {
|
||||
type = 'protected-session';
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
getContent() {
|
||||
// for auto-book cases when renaming title there should be content
|
||||
return "";
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -55,18 +55,21 @@ class NoteDetailCode {
|
||||
this.onNoteChange(() => this.ctx.noteChanged());
|
||||
}
|
||||
|
||||
// CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check)
|
||||
// we provide fallback
|
||||
this.codeEditor.setValue(this.ctx.note.content || "");
|
||||
// lazy loading above can take time and tab might have been already switched to another note
|
||||
if (this.ctx.note && this.ctx.note.type === 'code') {
|
||||
// CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check)
|
||||
// we provide fallback
|
||||
this.codeEditor.setValue(this.ctx.note.content || "");
|
||||
|
||||
const info = CodeMirror.findModeByMIME(this.ctx.note.mime);
|
||||
const info = CodeMirror.findModeByMIME(this.ctx.note.mime);
|
||||
|
||||
if (info) {
|
||||
this.codeEditor.setOption("mode", info.mime);
|
||||
CodeMirror.autoLoadMode(this.codeEditor, info.mode);
|
||||
if (info) {
|
||||
this.codeEditor.setOption("mode", info.mime);
|
||||
CodeMirror.autoLoadMode(this.codeEditor, info.mode);
|
||||
}
|
||||
|
||||
this.show();
|
||||
}
|
||||
|
||||
this.show();
|
||||
}
|
||||
|
||||
show() {
|
||||
|
||||
@@ -39,8 +39,11 @@ class NoteDetailFile {
|
||||
});
|
||||
|
||||
this.$uploadNewRevisionInput.on('change', async () => {
|
||||
const fileToUpload = this.$uploadNewRevisionInput[0].files[0]; // copy to allow reset below
|
||||
this.$uploadNewRevisionInput.val('');
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('upload', this.$uploadNewRevisionInput[0].files[0]);
|
||||
formData.append('upload', fileToUpload);
|
||||
|
||||
const result = await $.ajax({
|
||||
url: baseApiUrl + 'notes/' + this.ctx.note.noteId + '/file',
|
||||
@@ -87,8 +90,7 @@ class NoteDetailFile {
|
||||
}
|
||||
|
||||
getFileUrl() {
|
||||
// electron needs absolute URL so we extract current host, port, protocol
|
||||
return utils.getHost() + "/api/notes/" + this.ctx.note.noteId + "/download";
|
||||
return utils.getUrlForDownload("api/notes/" + this.ctx.note.noteId + "/download");
|
||||
}
|
||||
|
||||
show() {}
|
||||
|
||||
@@ -48,8 +48,11 @@ class NoteDetailImage {
|
||||
});
|
||||
|
||||
this.$uploadNewRevisionInput.on('change', async () => {
|
||||
const fileToUpload = this.$uploadNewRevisionInput[0].files[0]; // copy to allow reset below
|
||||
this.$uploadNewRevisionInput.val('');
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('upload', this.$uploadNewRevisionInput[0].files[0]);
|
||||
formData.append('upload', fileToUpload);
|
||||
|
||||
const result = await $.ajax({
|
||||
url: baseApiUrl + 'images/' + this.ctx.note.noteId,
|
||||
@@ -98,8 +101,7 @@ class NoteDetailImage {
|
||||
}
|
||||
|
||||
getFileUrl() {
|
||||
// electron needs absolute URL so we extract current host, port, protocol
|
||||
return utils.getHost() + `/api/notes/${this.ctx.note.noteId}/download`;
|
||||
return utils.getUrlForDownload(`api/notes/${this.ctx.note.noteId}/download`);
|
||||
}
|
||||
|
||||
show() {}
|
||||
|
||||
@@ -129,9 +129,10 @@ class NoteDetailRelationMap {
|
||||
return;
|
||||
}
|
||||
|
||||
const {note} = await server.post(`notes/${this.ctx.note.noteId}/children`, {
|
||||
const {note} = await server.post(`notes/${this.ctx.note.noteId}/children?target=into`, {
|
||||
title,
|
||||
target: 'into'
|
||||
content: '',
|
||||
type: 'text'
|
||||
});
|
||||
|
||||
toastService.showMessage("Click on canvas to place new note");
|
||||
@@ -159,7 +160,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');
|
||||
@@ -238,14 +239,17 @@ class NoteDetailRelationMap {
|
||||
|
||||
await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP);
|
||||
|
||||
this.loadMapData();
|
||||
|
||||
jsPlumb.ready(() => {
|
||||
this.initJsPlumbInstance();
|
||||
// lazy loading above can take time and tab might have been already switched to another note
|
||||
if (this.ctx.note && this.ctx.note.type === 'relation-map') {
|
||||
this.loadMapData();
|
||||
|
||||
this.initPanZoom();
|
||||
this.initJsPlumbInstance();
|
||||
|
||||
this.loadNotesAndRelations();
|
||||
this.initPanZoom();
|
||||
|
||||
this.loadNotesAndRelations();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
@@ -490,7 +494,7 @@ class NoteDetailRelationMap {
|
||||
}
|
||||
|
||||
async createNoteBox(noteId, title, x, y) {
|
||||
const $link = await linkService.createNoteLink(noteId, title);
|
||||
const $link = await linkService.createNoteLink(noteId, {title});
|
||||
$link.mousedown(e => {
|
||||
console.log(e);
|
||||
|
||||
|
||||
@@ -1,6 +1,42 @@
|
||||
import libraryLoader from "./library_loader.js";
|
||||
import treeService from './tree.js';
|
||||
import noteAutocompleteService from './note_autocomplete.js';
|
||||
import mimeTypesService from './mime_types.js';
|
||||
import treeCache from "./tree_cache.js";
|
||||
|
||||
const mentionSetup = {
|
||||
feeds: [
|
||||
{
|
||||
marker: '@',
|
||||
feed: queryText => {
|
||||
return new Promise((res, rej) => {
|
||||
noteAutocompleteService.autocompleteSource(queryText, rows => {
|
||||
if (rows.length === 1 && rows[0].title === 'No results') {
|
||||
rows = [];
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
row.text = row.name = row.noteTitle;
|
||||
row.id = '@' + row.text;
|
||||
row.link = '#' + row.path;
|
||||
}
|
||||
|
||||
res(rows);
|
||||
});
|
||||
});
|
||||
},
|
||||
itemRenderer: item => {
|
||||
const itemElement = document.createElement('span');
|
||||
|
||||
itemElement.classList.add('mentions-item');
|
||||
itemElement.innerHTML = `${item.highlightedTitle} `;
|
||||
|
||||
return itemElement;
|
||||
},
|
||||
minimumCharacters: 0
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
class NoteDetailText {
|
||||
/**
|
||||
@@ -26,13 +62,23 @@ class NoteDetailText {
|
||||
else {
|
||||
window.open(src, '_blank');
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async render() {
|
||||
if (!this.textEditor) {
|
||||
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
|
||||
|
||||
const codeBlockLanguages =
|
||||
(await mimeTypesService.getMimeTypes())
|
||||
.filter(mt => mt.enabled)
|
||||
.map(mt => {
|
||||
return {
|
||||
language: mt.mime.toLowerCase().replace(/[\W_]+/g,"-"),
|
||||
label: mt.title
|
||||
}
|
||||
});
|
||||
|
||||
// CKEditor since version 12 needs the element to be visible before initialization. At the same time
|
||||
// we want to avoid flicker - i.e. show editor only once everything is ready. That's why we have separate
|
||||
// display of $component in both branches.
|
||||
@@ -43,52 +89,29 @@ class NoteDetailText {
|
||||
if (!this.textEditor) {
|
||||
this.textEditor = await BalloonEditor.create(this.$editorEl[0], {
|
||||
placeholder: "Type the content of your note here ...",
|
||||
mention: {
|
||||
feeds: [
|
||||
{
|
||||
marker: '@',
|
||||
feed: queryText => {
|
||||
return new Promise((res, rej) => {
|
||||
noteAutocompleteService.autocompleteSource(queryText, rows => {
|
||||
if (rows.length === 1 && rows[0].title === 'No results') {
|
||||
rows = [];
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
row.text = row.name = row.noteTitle;
|
||||
row.id = '@' + row.text;
|
||||
row.link = '#' + row.path;
|
||||
}
|
||||
|
||||
console.log(rows.slice(0, Math.min(5, rows.length)));
|
||||
|
||||
res(rows);
|
||||
});
|
||||
});
|
||||
},
|
||||
itemRenderer: item => {
|
||||
const itemElement = document.createElement('span');
|
||||
|
||||
itemElement.classList.add('mentions-item');
|
||||
itemElement.innerHTML = `${item.highlightedTitle} `;
|
||||
|
||||
return itemElement;
|
||||
},
|
||||
minimumCharacters: 0
|
||||
}
|
||||
]
|
||||
mention: mentionSetup,
|
||||
codeBlock: {
|
||||
languages: codeBlockLanguages
|
||||
}
|
||||
});
|
||||
|
||||
if (glob.isDev) {
|
||||
await import('../../libraries/ckeditor/inspector.js');
|
||||
CKEditorInspector.attach(this.textEditor);
|
||||
}
|
||||
|
||||
this.onNoteChange(() => this.ctx.noteChanged());
|
||||
}
|
||||
}
|
||||
|
||||
this.textEditor.isReadOnly = await this.isReadOnly();
|
||||
// lazy loading above can take time and tab might have been already switched to another note
|
||||
if (this.ctx.note && this.ctx.note.type === 'text') {
|
||||
this.textEditor.isReadOnly = await this.isReadOnly();
|
||||
|
||||
this.$component.show();
|
||||
this.$component.show();
|
||||
|
||||
this.textEditor.setData(this.ctx.note.content);
|
||||
this.textEditor.setData(this.ctx.note.content);
|
||||
}
|
||||
}
|
||||
|
||||
getContent() {
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -142,6 +142,12 @@ async function refreshSearch() {
|
||||
toastService.showMessage("Saved search note refreshed.");
|
||||
}
|
||||
|
||||
function searchInSubtree(noteId) {
|
||||
showSearch();
|
||||
|
||||
$searchInput.val(`@in=${noteId} @text*=*`);
|
||||
}
|
||||
|
||||
function init() {
|
||||
const hashValue = document.location.hash ? document.location.hash.substr(1) : ""; // strip initial #
|
||||
|
||||
@@ -178,5 +184,6 @@ export default {
|
||||
refreshSearch,
|
||||
doSearch,
|
||||
init,
|
||||
searchInSubtree,
|
||||
getHelpText: () => helpText
|
||||
};
|
||||
@@ -42,12 +42,16 @@ async function remove(url, headers = {}) {
|
||||
let i = 1;
|
||||
const reqResolves = {};
|
||||
|
||||
let maxKnownSyncId = 0;
|
||||
|
||||
async function call(method, url, data, headers = {}) {
|
||||
let resp;
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const ipc = require('electron').ipcRenderer;
|
||||
const requestId = i++;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
resp = await new Promise((resolve, reject) => {
|
||||
reqResolves[requestId] = resolve;
|
||||
|
||||
if (REQUEST_LOGGING_ENABLED) {
|
||||
@@ -64,32 +68,58 @@ async function call(method, url, data, headers = {}) {
|
||||
});
|
||||
}
|
||||
else {
|
||||
return await ajax(url, method, data, headers);
|
||||
resp = await ajax(url, method, data, headers);
|
||||
}
|
||||
|
||||
const maxSyncIdStr = resp.headers['trilium-max-sync-id'];
|
||||
|
||||
if (maxSyncIdStr && maxSyncIdStr.trim()) {
|
||||
maxKnownSyncId = Math.max(maxKnownSyncId, parseInt(maxSyncIdStr));
|
||||
}
|
||||
|
||||
return resp.body;
|
||||
}
|
||||
|
||||
async function ajax(url, method, data, headers) {
|
||||
const options = {
|
||||
url: baseApiUrl + url,
|
||||
type: method,
|
||||
headers: getHeaders(headers),
|
||||
timeout: 60000
|
||||
};
|
||||
function ajax(url, method, data, headers) {
|
||||
return new Promise((res, rej) => {
|
||||
const options = {
|
||||
url: baseApiUrl + url,
|
||||
type: method,
|
||||
headers: getHeaders(headers),
|
||||
timeout: 60000,
|
||||
success: (body, textStatus, jqXhr) => {
|
||||
const respHeaders = {};
|
||||
|
||||
if (data) {
|
||||
try {
|
||||
options.data = JSON.stringify(data);
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Can't stringify data: ", data, " because of error: ", e)
|
||||
}
|
||||
options.contentType = "application/json";
|
||||
}
|
||||
jqXhr.getAllResponseHeaders().trim().split(/[\r\n]+/).forEach(line => {
|
||||
const parts = line.split(': ');
|
||||
const header = parts.shift();
|
||||
respHeaders[header] = parts.join(': ');
|
||||
});
|
||||
|
||||
return await $.ajax(options).catch(e => {
|
||||
const message = "Error when calling " + method + " " + url + ": " + e.status + " - " + e.statusText;
|
||||
toastService.showError(message);
|
||||
toastService.throwError(message);
|
||||
res({
|
||||
body,
|
||||
headers: respHeaders
|
||||
});
|
||||
},
|
||||
error: (jqXhr, textStatus, error) => {
|
||||
const message = "Error when calling " + method + " " + url + ": " + textStatus + " - " + error;
|
||||
toastService.showError(message);
|
||||
toastService.throwError(message);
|
||||
|
||||
rej(error);
|
||||
}
|
||||
};
|
||||
|
||||
if (data) {
|
||||
try {
|
||||
options.data = JSON.stringify(data);
|
||||
} catch (e) {
|
||||
console.log("Can't stringify data: ", data, " because of error: ", e)
|
||||
}
|
||||
options.contentType = "application/json";
|
||||
}
|
||||
|
||||
$.ajax(options);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -101,7 +131,10 @@ if (utils.isElectron()) {
|
||||
console.log(utils.now(), "Response #" + arg.requestId + ": " + arg.statusCode);
|
||||
}
|
||||
|
||||
reqResolves[arg.requestId](arg.body);
|
||||
reqResolves[arg.requestId]({
|
||||
body: arg.body,
|
||||
headers: arg.headers
|
||||
});
|
||||
|
||||
delete reqResolves[arg.requestId];
|
||||
});
|
||||
@@ -114,5 +147,6 @@ export default {
|
||||
remove,
|
||||
ajax,
|
||||
// don't remove, used from CKEditor image upload!
|
||||
getHeaders
|
||||
getHeaders,
|
||||
getMaxKnownSyncId: () => maxKnownSyncId
|
||||
};
|
||||
@@ -1,6 +1,47 @@
|
||||
import bundleService from "./bundle.js";
|
||||
import ws from "./ws.js";
|
||||
import optionsService from "./options.js";
|
||||
import splitService from "./split.js";
|
||||
import optionService from "./options.js";
|
||||
import server from "./server.js";
|
||||
import noteDetailService from "./note_detail.js";
|
||||
|
||||
const $sidebar = $("#right-pane");
|
||||
const $sidebarContainer = $('#sidebar-container');
|
||||
|
||||
const $showSidebarButton = $("#show-sidebar-button");
|
||||
const $hideSidebarButton = $("#hide-sidebar-button");
|
||||
|
||||
optionService.waitForOptions().then(options => toggleSidebar(options.is('rightPaneVisible')));
|
||||
|
||||
function toggleSidebar(show) {
|
||||
$sidebar.toggle(show);
|
||||
$showSidebarButton.toggle(!show);
|
||||
$hideSidebarButton.toggle(show);
|
||||
|
||||
if (show) {
|
||||
splitService.setupSplitWithSidebar();
|
||||
}
|
||||
else {
|
||||
splitService.setupSplitWithoutSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
$hideSidebarButton.on('click', () => {
|
||||
toggleSidebar(false);
|
||||
|
||||
server.put('options/rightPaneVisible/false');
|
||||
});
|
||||
|
||||
$showSidebarButton.on('click', async () => {
|
||||
toggleSidebar(true);
|
||||
|
||||
await server.put('options/rightPaneVisible/true');
|
||||
|
||||
const {sidebar} = noteDetailService.getActiveTabContext();
|
||||
await sidebar.noteLoaded();
|
||||
sidebar.show();
|
||||
});
|
||||
|
||||
class Sidebar {
|
||||
/**
|
||||
@@ -14,30 +55,18 @@ class Sidebar {
|
||||
widgets: []
|
||||
}, state);
|
||||
this.widgets = [];
|
||||
this.$sidebar = ctx.$tabContent.find(".note-detail-sidebar");
|
||||
this.$widgetContainer = this.$sidebar.find(".note-detail-widget-container");
|
||||
this.$showSideBarButton = this.ctx.$tabContent.find(".show-sidebar-button");
|
||||
this.$hideSidebarButton = this.$sidebar.find(".hide-sidebar-button");
|
||||
|
||||
this.$hideSidebarButton.on('click', () => {
|
||||
this.$sidebar.hide();
|
||||
this.$showSideBarButton.show();
|
||||
this.ctx.stateChanged();
|
||||
});
|
||||
this.$widgetContainer = $sidebar.find(`.sidebar-widget-container[data-tab-id=${this.ctx.tabId}]`);
|
||||
|
||||
this.$showSideBarButton.on('click', () => {
|
||||
this.$sidebar.show();
|
||||
this.$showSideBarButton.hide();
|
||||
this.ctx.stateChanged();
|
||||
this.noteLoaded();
|
||||
});
|
||||
if (this.$widgetContainer.length === 0) {
|
||||
this.$widgetContainer = $(`<div class="sidebar-widget-container">`).attr('data-tab-id', this.ctx.tabId);
|
||||
|
||||
this.$showSideBarButton.toggle(!state.visible);
|
||||
this.$sidebar.toggle(state.visible);
|
||||
$sidebarContainer.append(this.$widgetContainer);
|
||||
}
|
||||
}
|
||||
|
||||
isVisible() {
|
||||
return this.$sidebar.css("display") !== "none";
|
||||
return $sidebar.css("display") !== "none";
|
||||
}
|
||||
|
||||
getSidebarState() {
|
||||
@@ -91,6 +120,12 @@ class Sidebar {
|
||||
this.renderWidgets(widgets);
|
||||
}
|
||||
|
||||
show() {
|
||||
$sidebarContainer.find('.sidebar-widget-container').each((i, el) => {
|
||||
$(el).toggle($(el).attr('data-tab-id') === this.ctx.tabId);
|
||||
});
|
||||
}
|
||||
|
||||
// it's important that this method is sync so that the whole render-update is atomic
|
||||
// otherwise we could get race issues (doubled widgets etc.)
|
||||
renderWidgets(widgets) {
|
||||
@@ -119,6 +154,12 @@ class Sidebar {
|
||||
this.$widgetContainer.empty().append(...widgetsToAppend);
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (this.$widgetContainer) {
|
||||
this.$widgetContainer.remove();
|
||||
}
|
||||
}
|
||||
|
||||
eventReceived(name, data) {
|
||||
for (const widget of this.widgets) {
|
||||
if (widget.eventReceived) {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import server from "./server.js";
|
||||
import optionService from "./options.js";
|
||||
|
||||
let instance;
|
||||
|
||||
async function getPaneWidths() {
|
||||
const options = await optionService.waitForOptions();
|
||||
|
||||
return {
|
||||
leftPaneWidth: options.getInt('leftPaneWidth'),
|
||||
rightPaneWidth: options.getInt('rightPaneWidth')
|
||||
};
|
||||
}
|
||||
|
||||
async function setupSplitWithSidebar() {
|
||||
if (instance) {
|
||||
instance.destroy();
|
||||
}
|
||||
|
||||
const {leftPaneWidth, rightPaneWidth} = await getPaneWidths();
|
||||
|
||||
instance = Split(['#left-pane', '#center-pane', '#right-pane'], {
|
||||
sizes: [leftPaneWidth, 100 - leftPaneWidth - rightPaneWidth, rightPaneWidth],
|
||||
gutterSize: 5,
|
||||
onDragEnd: sizes => {
|
||||
server.put('options/leftPaneWidth/' + Math.round(sizes[0]));
|
||||
server.put('options/rightPaneWidth/' + Math.round(sizes[2]));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function setupSplitWithoutSidebar() {
|
||||
if (instance) {
|
||||
instance.destroy();
|
||||
}
|
||||
|
||||
const {leftPaneWidth} = await getPaneWidths();
|
||||
|
||||
instance = Split(['#left-pane', '#center-pane'], {
|
||||
sizes: [leftPaneWidth, 100 - leftPaneWidth],
|
||||
gutterSize: 5,
|
||||
onDragEnd: sizes => {
|
||||
server.put('options/leftPaneWidth/' + Math.round(sizes[0]));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
setupSplitWithSidebar,
|
||||
setupSplitWithoutSidebar
|
||||
};
|
||||
@@ -148,6 +148,10 @@ class TabContext {
|
||||
|
||||
this.setCurrentNotePathToHash();
|
||||
|
||||
if (this.sidebar) {
|
||||
this.sidebar.noteLoaded(); // load async
|
||||
}
|
||||
|
||||
this.noteChangeDisabled = true;
|
||||
|
||||
try {
|
||||
@@ -181,10 +185,6 @@ class TabContext {
|
||||
this.noteType.update();
|
||||
}
|
||||
|
||||
if (this.sidebar) {
|
||||
this.sidebar.noteLoaded(); // load async
|
||||
}
|
||||
|
||||
bundleService.executeRelationBundles(this.note, 'runOnNoteView', this);
|
||||
}
|
||||
|
||||
@@ -204,6 +204,10 @@ class TabContext {
|
||||
|
||||
this.$tabContent.show();
|
||||
|
||||
if (this.sidebar) {
|
||||
this.sidebar.show();
|
||||
}
|
||||
|
||||
this.setCurrentNotePathToHash();
|
||||
this.setTitleBar();
|
||||
}
|
||||
@@ -246,11 +250,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') {
|
||||
@@ -281,6 +289,10 @@ class TabContext {
|
||||
}
|
||||
|
||||
getComponent() {
|
||||
if (!this.components[this.type]) {
|
||||
throw new Error("Could not find component for type: " + this.type);
|
||||
}
|
||||
|
||||
return this.components[this.type];
|
||||
}
|
||||
|
||||
@@ -367,7 +379,7 @@ class TabContext {
|
||||
async addPath(notePath, isCurrent) {
|
||||
const title = await treeUtils.getNotePathTitle(notePath);
|
||||
|
||||
const noteLink = await linkService.createNoteLink(notePath, title);
|
||||
const noteLink = await linkService.createNoteLink(notePath, {title});
|
||||
|
||||
noteLink
|
||||
.addClass("no-tooltip-preview")
|
||||
@@ -407,6 +419,16 @@ class TabContext {
|
||||
|
||||
await this.addPath(notePath, isCurrent);
|
||||
}
|
||||
|
||||
const cloneLink = $("<div>")
|
||||
.addClass("dropdown-item")
|
||||
.append(
|
||||
$('<button class="btn btn-sm">')
|
||||
.text('Clone note to new location...')
|
||||
.on('click', () => import("../dialogs/clone_to.js").then(d => d.showDialog([this.note.noteId])))
|
||||
);
|
||||
|
||||
this.$notePathList.append(cloneLink);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,6 +440,10 @@ class TabContext {
|
||||
await this.saveNoteIfChanged();
|
||||
this.$tabContent.remove();
|
||||
}
|
||||
|
||||
if (this.sidebar) {
|
||||
this.sidebar.remove();
|
||||
}
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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");
|
||||
@@ -128,16 +127,16 @@ async function getNodeFromPath(notePath, expand = false, expandOpts = {}) {
|
||||
|
||||
// we expand only after hoisted note since before then nodes are not actually present in the tree
|
||||
if (parentNode) {
|
||||
checkFolderStatus(parentNode);
|
||||
|
||||
if (!parentNode.isLoaded()) {
|
||||
await parentNode.load();
|
||||
}
|
||||
|
||||
if (expand) {
|
||||
parentNode.setExpanded(true, expandOpts);
|
||||
await parentNode.setExpanded(true, expandOpts);
|
||||
}
|
||||
|
||||
await checkFolderStatus(parentNode);
|
||||
|
||||
let foundChildNode = findChildNode(parentNode, childNoteId);
|
||||
|
||||
if (!foundChildNode) { // note might be recently created so we'll force reload and try again
|
||||
@@ -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);
|
||||
|
||||
@@ -41,7 +41,10 @@ const NOTE_TYPE_ICONS = {
|
||||
async function getIcon(note) {
|
||||
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
||||
|
||||
if (note.noteId === 'root') {
|
||||
if (note.iconClass) {
|
||||
return note.iconClass;
|
||||
}
|
||||
else if (note.noteId === 'root') {
|
||||
return "bx bx-chevrons-right";
|
||||
}
|
||||
else if (note.noteId === hoistedNoteId) {
|
||||
@@ -62,6 +65,11 @@ async function getIcon(note) {
|
||||
|
||||
async function prepareNode(branch) {
|
||||
const note = await branch.getNote();
|
||||
|
||||
if (!note) {
|
||||
throw new Error(`Branch has no note ` + branch.noteId);
|
||||
}
|
||||
|
||||
const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title;
|
||||
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
||||
|
||||
|
||||
@@ -107,6 +107,10 @@ class TreeCache {
|
||||
if (note.type === 'search') {
|
||||
const searchResults = await server.get('search-note/' + note.noteId);
|
||||
|
||||
if (!searchResults) {
|
||||
throw new Error(`Search note ${note.noteId} failed.`);
|
||||
}
|
||||
|
||||
// force to load all the notes at once instead of one by one
|
||||
await treeCache.getNotes(searchResults.map(res => res.noteId));
|
||||
|
||||
@@ -129,7 +133,7 @@ class TreeCache {
|
||||
|
||||
/** @return {Promise<NoteShort[]>} */
|
||||
async getNotes(noteIds, silentNotFoundError = false) {
|
||||
const missingNoteIds = noteIds.filter(noteId => this.notes[noteId] === undefined);
|
||||
const missingNoteIds = noteIds.filter(noteId => !this.notes[noteId]);
|
||||
|
||||
if (missingNoteIds.length > 0) {
|
||||
await this.reloadNotes(missingNoteIds);
|
||||
@@ -144,7 +148,7 @@ class TreeCache {
|
||||
else {
|
||||
return this.notes[noteId];
|
||||
}
|
||||
}).filter(note => note !== null);
|
||||
}).filter(note => !!note);
|
||||
}
|
||||
|
||||
/** @return {Promise<boolean>} */
|
||||
@@ -157,6 +161,11 @@ class TreeCache {
|
||||
/** @return {Promise<NoteShort>} */
|
||||
async getNote(noteId, silentNotFoundError = false) {
|
||||
if (noteId === 'none') {
|
||||
console.log(`No 'none' note.`);
|
||||
return null;
|
||||
}
|
||||
else if (!noteId) {
|
||||
console.log(`Falsy noteId ${noteId}, returning null.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import hoistedNoteService from './hoisted_note.js';
|
||||
import noteDetailService from './note_detail.js';
|
||||
import clipboard from './clipboard.js';
|
||||
import protectedSessionHolder from "./protected_session_holder.js";
|
||||
import searchNotesService from "./search_notes.js";
|
||||
|
||||
class TreeContextMenu {
|
||||
constructor(node) {
|
||||
@@ -41,51 +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: "----" },
|
||||
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",
|
||||
{ title: 'Search in subtree <kbd data-kb-action="SearchInSubtree"></kbd>', cmd: "searchInSubtree", uiIcon: "search",
|
||||
enabled: notSearch && noSelectedNotes },
|
||||
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",
|
||||
enabled: !clipboard.isEmpty() && notSearch && noSelectedNotes },
|
||||
{ title: "Paste after", cmd: "pasteAfter", uiIcon: "paste",
|
||||
enabled: !clipboard.isEmpty() && isNotRoot && parentNotSearch && noSelectedNotes },
|
||||
{ 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",
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -93,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;
|
||||
@@ -177,6 +181,9 @@ class TreeContextMenu {
|
||||
|
||||
treeService.duplicateNote(this.node.data.noteId, branch.parentNoteId);
|
||||
}
|
||||
else if (cmd === "searchInSubtree") {
|
||||
searchNotesService.searchInSubtree(this.node.data.noteId);
|
||||
}
|
||||
else {
|
||||
ws.logError("Unknown command: " + cmd);
|
||||
}
|
||||
|
||||
@@ -3,149 +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 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.
|
||||
@@ -171,4 +32,149 @@ const keyBindings = {
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
@@ -214,6 +223,20 @@ async function clearBrowserCache() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url - should be without initial slash!!!
|
||||
*/
|
||||
function getUrlForDownload(url) {
|
||||
if (isElectron()) {
|
||||
// electron needs absolute URL so we extract current host, port, protocol
|
||||
return getHost() + '/' + url;
|
||||
}
|
||||
else {
|
||||
// web server can be deployed on subdomain so we need to use relative path
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
reloadApp,
|
||||
parseDate,
|
||||
@@ -230,7 +253,6 @@ export default {
|
||||
escapeHtml,
|
||||
stopWatch,
|
||||
formatLabel,
|
||||
getHost,
|
||||
download,
|
||||
toObject,
|
||||
randomString,
|
||||
@@ -245,5 +267,7 @@ export default {
|
||||
getMimeTypeClass,
|
||||
closeActiveDialog,
|
||||
isHtmlEmpty,
|
||||
clearBrowserCache
|
||||
clearBrowserCache,
|
||||
getUrlForDownload,
|
||||
normalizeShortcut
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user