mirror of
https://github.com/zadam/trilium.git
synced 2025-10-26 15:56:29 +01:00
Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0395e9866 | ||
|
|
89aa4fbc73 | ||
|
|
74a7802088 | ||
|
|
a89b6711d1 | ||
|
|
b2549b2834 | ||
|
|
959c4cbe64 | ||
|
|
d03d3603d2 | ||
|
|
e1c2573778 | ||
|
|
2af2b45062 | ||
|
|
eabe4775bd | ||
|
|
da9b321aa0 | ||
|
|
c18d8d2d1b | ||
|
|
5f2361ebd5 | ||
|
|
9791dab97d | ||
|
|
85d986534d | ||
|
|
00faf758e8 | ||
|
|
6ba2e5cf73 | ||
|
|
fb3876d28b | ||
|
|
fb975849b9 | ||
|
|
16fef78344 | ||
|
|
e0b4b369dc | ||
|
|
0df7851214 | ||
|
|
5d47c2b23e | ||
|
|
d09b021487 | ||
|
|
910bda860c | ||
|
|
2d92b4931a | ||
|
|
212b719ee9 | ||
|
|
1db892d22f | ||
|
|
535dcb6d12 | ||
|
|
4426362799 | ||
|
|
2c609e8136 | ||
|
|
11b73b79ed | ||
|
|
e70c862e72 | ||
|
|
b3e66d5a83 | ||
|
|
e8cd821e57 | ||
|
|
be7ac74235 | ||
|
|
58fa0832f6 | ||
|
|
1502b9ce66 | ||
|
|
7307ca385f | ||
|
|
c1fd9825aa | ||
|
|
9de7d3fc53 | ||
|
|
3c5db844ba | ||
|
|
e7330c1104 | ||
|
|
ec4586b164 | ||
|
|
91e5f24798 | ||
|
|
38723e0189 | ||
|
|
8c88ce6f65 | ||
|
|
4d22959e28 | ||
|
|
50a28d8c51 | ||
|
|
e25b633ec4 | ||
|
|
75bd395669 | ||
|
|
5e353a5612 | ||
|
|
6b359b7796 | ||
|
|
13f9d037dc | ||
|
|
1911d64c1c | ||
|
|
d4c3f1b3f2 | ||
|
|
3db84daf94 | ||
|
|
2526715aa4 | ||
|
|
04c573e212 | ||
|
|
58f4f5d1e6 | ||
|
|
fa5d982a55 | ||
|
|
108afe8896 | ||
|
|
37da0adb8a | ||
|
|
4f50864ec8 | ||
|
|
30b9ef8604 | ||
|
|
b063b4c528 | ||
|
|
e08b0141a4 | ||
|
|
9d8b8e26a1 | ||
|
|
cb70109ee7 | ||
|
|
e541abbd60 | ||
|
|
940a70adc5 | ||
|
|
88e8eb7e9c | ||
|
|
b365c186a1 | ||
|
|
64c9734f05 | ||
|
|
48c843c087 | ||
|
|
0e4eec10b9 | ||
|
|
a3661cb763 | ||
|
|
115879ec4a | ||
|
|
df11b076bc | ||
|
|
54ecd2ee75 | ||
|
|
2369bcf9fc | ||
|
|
5d8808a2ad | ||
|
|
62b993f06f | ||
|
|
8aa5608085 | ||
|
|
b452d7e5c5 | ||
|
|
9b9d6d86d0 | ||
|
|
7f2755d4a0 | ||
|
|
3b268cc8eb | ||
|
|
6dfe335707 | ||
|
|
c7125d2b50 | ||
|
|
e8a33a5ee7 | ||
|
|
deb0b24c4c | ||
|
|
cafcb67a8a | ||
|
|
4c6e9480e4 | ||
|
|
109bead1c7 | ||
|
|
ae1220b970 | ||
|
|
b89a2df462 | ||
|
|
8b5536ee3a | ||
|
|
647790885d | ||
|
|
227c3e4dcc | ||
|
|
4eb2407c73 | ||
|
|
43e12fbea2 | ||
|
|
2a3091f788 | ||
|
|
742df25bc2 | ||
|
|
9be1d1f697 | ||
|
|
ed52f93bbb | ||
|
|
3466a19397 | ||
|
|
fe53e2351c | ||
|
|
5c35b870eb | ||
|
|
90d091aedb | ||
|
|
0a05a40186 | ||
|
|
7482ed063b | ||
|
|
70e343f2fc | ||
|
|
358f3a7291 | ||
|
|
6161b1c193 | ||
|
|
94b57dadd7 | ||
|
|
81fbefb9cd | ||
|
|
6d6695e3a9 | ||
|
|
4c308ad68f | ||
|
|
989a003d2f | ||
|
|
ccdb41841e | ||
|
|
0a94622413 | ||
|
|
5769587305 | ||
|
|
ffbfccb701 | ||
|
|
56ce23fc36 | ||
|
|
cba7e5a59f | ||
|
|
86cf8f3202 | ||
|
|
907cdd8fcb | ||
|
|
7ea53d468e | ||
|
|
586d6b4557 | ||
|
|
8f68ff1932 | ||
|
|
a1ea2c9115 | ||
|
|
e8ce81a133 | ||
|
|
aff12950f0 | ||
|
|
75c58cbf79 | ||
|
|
87a1e98fa2 | ||
|
|
d1eacbb574 | ||
|
|
71d248cd87 | ||
|
|
ac608b9334 | ||
|
|
32020d78b5 | ||
|
|
ff853c7d0a | ||
|
|
8526cb2315 | ||
|
|
dc89f72e75 | ||
|
|
657ff16267 | ||
|
|
ed759f5585 | ||
|
|
a86177bb59 | ||
|
|
9f1b3cc892 | ||
|
|
8473f72ec8 | ||
|
|
666d202a3a | ||
|
|
988fae50cb | ||
|
|
98bbd17920 | ||
|
|
dadcc93ae3 | ||
|
|
48e19d0149 |
4
.idea/dataSources.xml
generated
4
.idea/dataSources.xml
generated
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="document.db" uuid="a2c75661-f9e2-478f-a69f-6a9409e69997">
|
||||
<data-source source="LOCAL" name="document.db" uuid="b0b03187-36c8-4ec1-bdab-fd4273cd692e">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/../trilium-data/document.db</jdbc-url>
|
||||
<jdbc-url>jdbc:sqlite:$USER_HOME$/trilium-data/document.db</jdbc-url>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,662 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<dataSource name="document.db">
|
||||
<database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.17">
|
||||
<root id="1">
|
||||
<ServerVersion>3.25.1</ServerVersion>
|
||||
</root>
|
||||
<schema id="2" parent="1" name="main">
|
||||
<Current>1</Current>
|
||||
</schema>
|
||||
<collation id="3" parent="1" name="BINARY"/>
|
||||
<collation id="4" parent="1" name="NOCASE"/>
|
||||
<collation id="5" parent="1" name="RTRIM"/>
|
||||
<table id="6" parent="2" name="api_tokens"/>
|
||||
<table id="7" parent="2" name="attributes"/>
|
||||
<table id="8" parent="2" name="branches"/>
|
||||
<table id="9" parent="2" name="note_contents"/>
|
||||
<table id="10" parent="2" name="note_revision_contents"/>
|
||||
<table id="11" parent="2" name="note_revisions"/>
|
||||
<table id="12" parent="2" name="notes"/>
|
||||
<table id="13" parent="2" name="options"/>
|
||||
<table id="14" parent="2" name="recent_notes"/>
|
||||
<table id="15" parent="2" name="source_ids"/>
|
||||
<table id="16" parent="2" name="sqlite_master">
|
||||
<System>1</System>
|
||||
</table>
|
||||
<table id="17" parent="2" name="sqlite_sequence">
|
||||
<System>1</System>
|
||||
</table>
|
||||
<table id="18" parent="2" name="sync"/>
|
||||
<column id="19" parent="6" name="apiTokenId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="20" parent="6" name="token">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="21" parent="6" name="utcDateCreated">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="22" parent="6" name="isDeleted">
|
||||
<Position>4</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="23" parent="6" name="hash">
|
||||
<Position>5</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>""</DefaultExpression>
|
||||
</column>
|
||||
<index id="24" parent="6" name="sqlite_autoindex_api_tokens_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>apiTokenId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<key id="25" parent="6">
|
||||
<ColNames>apiTokenId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_api_tokens_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="26" parent="7" name="attributeId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="27" parent="7" name="noteId">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="28" parent="7" name="type">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="29" parent="7" name="name">
|
||||
<Position>4</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="30" parent="7" name="value">
|
||||
<Position>5</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>''</DefaultExpression>
|
||||
</column>
|
||||
<column id="31" parent="7" name="position">
|
||||
<Position>6</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="32" parent="7" name="utcDateCreated">
|
||||
<Position>7</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="33" parent="7" name="utcDateModified">
|
||||
<Position>8</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="34" parent="7" name="isDeleted">
|
||||
<Position>9</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="35" parent="7" name="deleteId">
|
||||
<Position>10</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<DefaultExpression>NULL</DefaultExpression>
|
||||
</column>
|
||||
<column id="36" parent="7" name="hash">
|
||||
<Position>11</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>""</DefaultExpression>
|
||||
</column>
|
||||
<column id="37" parent="7" name="isInheritable">
|
||||
<Position>12</Position>
|
||||
<DataType>int|0s</DataType>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<index id="38" parent="7" name="sqlite_autoindex_attributes_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>attributeId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="39" parent="7" name="IDX_attributes_noteId_index">
|
||||
<ColNames>noteId</ColNames>
|
||||
</index>
|
||||
<index id="40" parent="7" name="IDX_attributes_name_value">
|
||||
<ColNames>name
|
||||
value</ColNames>
|
||||
</index>
|
||||
<index id="41" parent="7" name="IDX_attributes_value_index">
|
||||
<ColNames>value</ColNames>
|
||||
</index>
|
||||
<key id="42" parent="7">
|
||||
<ColNames>attributeId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_attributes_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="43" parent="8" name="branchId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="44" parent="8" name="noteId">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="45" parent="8" name="parentNoteId">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="46" parent="8" name="notePosition">
|
||||
<Position>4</Position>
|
||||
<DataType>INTEGER|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="47" parent="8" name="prefix">
|
||||
<Position>5</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
</column>
|
||||
<column id="48" 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">
|
||||
<Position>7</Position>
|
||||
<DataType>INTEGER|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="50" parent="8" name="deleteId">
|
||||
<Position>8</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<DefaultExpression>NULL</DefaultExpression>
|
||||
</column>
|
||||
<column id="51" parent="8" name="utcDateModified">
|
||||
<Position>9</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="52" parent="8" name="utcDateCreated">
|
||||
<Position>10</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="53" parent="8" name="hash">
|
||||
<Position>11</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>""</DefaultExpression>
|
||||
</column>
|
||||
<index id="54" parent="8" name="sqlite_autoindex_branches_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>branchId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="55" parent="8" name="IDX_branches_noteId_parentNoteId">
|
||||
<ColNames>noteId
|
||||
parentNoteId</ColNames>
|
||||
</index>
|
||||
<index id="56" parent="8" name="IDX_branches_parentNoteId">
|
||||
<ColNames>parentNoteId</ColNames>
|
||||
</index>
|
||||
<key id="57" parent="8">
|
||||
<ColNames>branchId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="58" parent="9" name="noteId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="59" parent="9" name="content">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<DefaultExpression>NULL</DefaultExpression>
|
||||
</column>
|
||||
<column id="60" parent="9" name="hash">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>""</DefaultExpression>
|
||||
</column>
|
||||
<column id="61" 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">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>noteId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<key id="63" parent="9">
|
||||
<ColNames>noteId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_note_contents_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="64" parent="10" name="noteRevisionId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="65" parent="10" name="content">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
</column>
|
||||
<column id="66" parent="10" name="hash">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>''</DefaultExpression>
|
||||
</column>
|
||||
<column id="67" 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">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>noteRevisionId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<key id="69" parent="10">
|
||||
<ColNames>noteRevisionId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_note_revision_contents_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="70" parent="11" name="noteRevisionId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="71" parent="11" name="noteId">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="72" parent="11" name="title">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
</column>
|
||||
<column id="73" parent="11" name="contentLength">
|
||||
<Position>4</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="74" 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">
|
||||
<Position>6</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="76" parent="11" name="utcDateLastEdited">
|
||||
<Position>7</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="77" parent="11" name="utcDateCreated">
|
||||
<Position>8</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="78" parent="11" name="utcDateModified">
|
||||
<Position>9</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="79" parent="11" name="dateLastEdited">
|
||||
<Position>10</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="80" parent="11" name="dateCreated">
|
||||
<Position>11</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="81" parent="11" name="type">
|
||||
<Position>12</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>''</DefaultExpression>
|
||||
</column>
|
||||
<column id="82" parent="11" name="mime">
|
||||
<Position>13</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>''</DefaultExpression>
|
||||
</column>
|
||||
<column id="83" 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">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>noteRevisionId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="85" parent="11" name="IDX_note_revisions_noteId">
|
||||
<ColNames>noteId</ColNames>
|
||||
</index>
|
||||
<index id="86" parent="11" name="IDX_note_revisions_utcDateLastEdited">
|
||||
<ColNames>utcDateLastEdited</ColNames>
|
||||
</index>
|
||||
<index id="87" parent="11" name="IDX_note_revisions_utcDateCreated">
|
||||
<ColNames>utcDateCreated</ColNames>
|
||||
</index>
|
||||
<index id="88" parent="11" name="IDX_note_revisions_dateLastEdited">
|
||||
<ColNames>dateLastEdited</ColNames>
|
||||
</index>
|
||||
<index id="89" parent="11" name="IDX_note_revisions_dateCreated">
|
||||
<ColNames>dateCreated</ColNames>
|
||||
</index>
|
||||
<key id="90" parent="11">
|
||||
<ColNames>noteRevisionId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="91" parent="12" name="noteId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="92" 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">
|
||||
<Position>3</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="94" 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">
|
||||
<Position>5</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>'text'</DefaultExpression>
|
||||
</column>
|
||||
<column id="96" 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">
|
||||
<Position>7</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>""</DefaultExpression>
|
||||
</column>
|
||||
<column id="98" parent="12" name="isDeleted">
|
||||
<Position>8</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="99" parent="12" name="deleteId">
|
||||
<Position>9</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<DefaultExpression>NULL</DefaultExpression>
|
||||
</column>
|
||||
<column id="100" parent="12" name="isErased">
|
||||
<Position>10</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="101" parent="12" name="dateCreated">
|
||||
<Position>11</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="102" parent="12" name="dateModified">
|
||||
<Position>12</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="103" parent="12" name="utcDateCreated">
|
||||
<Position>13</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="104" parent="12" name="utcDateModified">
|
||||
<Position>14</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="105" parent="12" name="sqlite_autoindex_notes_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>noteId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="106" parent="12" name="IDX_notes_title">
|
||||
<ColNames>title</ColNames>
|
||||
</index>
|
||||
<index id="107" parent="12" name="IDX_notes_type">
|
||||
<ColNames>type</ColNames>
|
||||
</index>
|
||||
<index id="108" parent="12" name="IDX_notes_isDeleted">
|
||||
<ColNames>isDeleted</ColNames>
|
||||
</index>
|
||||
<index id="109" parent="12" name="IDX_notes_dateCreated">
|
||||
<ColNames>dateCreated</ColNames>
|
||||
</index>
|
||||
<index id="110" parent="12" name="IDX_notes_dateModified">
|
||||
<ColNames>dateModified</ColNames>
|
||||
</index>
|
||||
<index id="111" parent="12" name="IDX_notes_utcDateCreated">
|
||||
<ColNames>utcDateCreated</ColNames>
|
||||
</index>
|
||||
<index id="112" parent="12" name="IDX_notes_utcDateModified">
|
||||
<ColNames>utcDateModified</ColNames>
|
||||
</index>
|
||||
<key id="113" parent="12">
|
||||
<ColNames>noteId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="114" parent="13" name="name">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="115" parent="13" name="value">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
</column>
|
||||
<column id="116" parent="13" name="isSynced">
|
||||
<Position>3</Position>
|
||||
<DataType>INTEGER|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="117" parent="13" name="hash">
|
||||
<Position>4</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>""</DefaultExpression>
|
||||
</column>
|
||||
<column id="118" parent="13" name="utcDateCreated">
|
||||
<Position>5</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="119" parent="13" name="utcDateModified">
|
||||
<Position>6</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="120" parent="13" name="sqlite_autoindex_options_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>name</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<key id="121" parent="13">
|
||||
<ColNames>name</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="122" parent="14" name="noteId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="123" parent="14" name="notePath">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="124" parent="14" name="hash">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>""</DefaultExpression>
|
||||
</column>
|
||||
<column id="125" parent="14" name="utcDateCreated">
|
||||
<Position>4</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="126" parent="14" name="isDeleted">
|
||||
<Position>5</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
</column>
|
||||
<index id="127" parent="14" name="sqlite_autoindex_recent_notes_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>noteId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<key id="128" parent="14">
|
||||
<ColNames>noteId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="129" parent="15" name="sourceId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="130" parent="15" name="utcDateCreated">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="131" parent="15" name="sqlite_autoindex_source_ids_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>sourceId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="132" parent="15" name="IDX_source_ids_utcDateCreated">
|
||||
<ColNames>utcDateCreated</ColNames>
|
||||
</index>
|
||||
<key id="133" parent="15">
|
||||
<ColNames>sourceId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="134" parent="16" name="type">
|
||||
<Position>1</Position>
|
||||
<DataType>text|0s</DataType>
|
||||
</column>
|
||||
<column id="135" parent="16" name="name">
|
||||
<Position>2</Position>
|
||||
<DataType>text|0s</DataType>
|
||||
</column>
|
||||
<column id="136" parent="16" name="tbl_name">
|
||||
<Position>3</Position>
|
||||
<DataType>text|0s</DataType>
|
||||
</column>
|
||||
<column id="137" parent="16" name="rootpage">
|
||||
<Position>4</Position>
|
||||
<DataType>int|0s</DataType>
|
||||
</column>
|
||||
<column id="138" parent="16" name="sql">
|
||||
<Position>5</Position>
|
||||
<DataType>text|0s</DataType>
|
||||
</column>
|
||||
<column id="139" parent="17" name="name">
|
||||
<Position>1</Position>
|
||||
</column>
|
||||
<column id="140" parent="17" name="seq">
|
||||
<Position>2</Position>
|
||||
</column>
|
||||
<column id="141" parent="18" name="id">
|
||||
<Position>1</Position>
|
||||
<DataType>INTEGER|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<SequenceIdentity>1</SequenceIdentity>
|
||||
</column>
|
||||
<column id="142" parent="18" name="entityName">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="143" parent="18" name="entityId">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="144" parent="18" name="sourceId">
|
||||
<Position>4</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="145" parent="18" name="isSynced">
|
||||
<Position>5</Position>
|
||||
<DataType>INTEGER|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="146" parent="18" name="utcSyncDate">
|
||||
<Position>6</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="147" parent="18" name="IDX_sync_entityName_entityId">
|
||||
<ColNames>entityName
|
||||
entityId</ColNames>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="148" parent="18" name="IDX_sync_utcSyncDate">
|
||||
<ColNames>utcSyncDate</ColNames>
|
||||
</index>
|
||||
<key id="149" parent="18">
|
||||
<ColNames>id</ColNames>
|
||||
<Primary>1</Primary>
|
||||
</key>
|
||||
</database-model>
|
||||
</dataSource>
|
||||
@@ -1,2 +0,0 @@
|
||||
#n:main
|
||||
!<md> [0, 0, null, null, -2147483648, -2147483648]
|
||||
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,6 +1,7 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="JSUnfilteredForInLoop" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
|
||||
1
.idea/vcs.xml
generated
1
.idea/vcs.xml
generated
@@ -2,5 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:12.16.2-alpine
|
||||
FROM node:12.16.3-alpine
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
@@ -8,7 +8,7 @@ Trilium Notes is a hierarchical note taking application with focus on building l
|
||||
## Features
|
||||
|
||||
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://github.com/zadam/trilium/wiki/Cloning-notes))
|
||||
* Rich WYSIWYG note editing including e.g. tables and images with markdown [autoformat](https://github.com/zadam/trilium/wiki/Text-editor#autoformat)
|
||||
* Rich WYSIWYG note editing including e.g. tables and images with markdown [autoformat](https://github.com/zadam/trilium/wiki/Text-notes#autoformat)
|
||||
* Support for editing [notes with source code](https://github.com/zadam/trilium/wiki/Code-notes), including syntax highlighting
|
||||
* Fast and easy [navigation between notes](https://github.com/zadam/trilium/wiki/Note-navigation), full text search and [note hoisting](https://github.com/zadam/trilium/wiki/Note-hoisting)
|
||||
* Seamless [note versioning](https://github.com/zadam/trilium/wiki/Note-revisions)
|
||||
|
||||
@@ -12,7 +12,7 @@ echo "Copying required linux-x64 binaries"
|
||||
rm -r $SRC_DIR/node_modules/sqlite3/lib/binding/*
|
||||
rm -r $SRC_DIR/node_modules/pngquant-bin/vendor/*
|
||||
|
||||
rm -r $SRC_DIR/src/public/dist/*.mobile.*
|
||||
rm -r $SRC_DIR/src/public/app-dist/*.mobile.*
|
||||
|
||||
cp -r bin/deps/linux-x64/sqlite/* $SRC_DIR/node_modules/sqlite3/lib/binding/
|
||||
cp bin/deps/linux-x64/image/pngquant $SRC_DIR/node_modules/pngquant-bin/vendor/
|
||||
@@ -29,6 +29,9 @@ cp images/app-icons/png/128x128.png $BUILD_DIR/icon.png
|
||||
# removing software WebGL binaries because they are pretty huge and not necessary
|
||||
rm -r $BUILD_DIR/swiftshader
|
||||
|
||||
cp bin/tpl/portable-trilium.sh $BUILD_DIR/
|
||||
chmod 755 $BUILD_DIR/portable-trilium.sh
|
||||
|
||||
echo "Packaging linux x64 electron distribution..."
|
||||
VERSION=`jq -r ".version" package.json`
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ cp bin/deps/mac-x64/image/cjpeg $SRC_DIR/node_modules/mozjpeg/vendor/
|
||||
cp bin/deps/mac-x64/image/pngquant $SRC_DIR/node_modules/pngquant-bin/vendor/
|
||||
cp bin/deps/mac-x64/image/gifsicle $SRC_DIR/node_modules/giflossy/vendor/
|
||||
|
||||
rm -r $SRC_DIR/src/public/dist/*.mobile.*
|
||||
rm -r $SRC_DIR/src/public/app-dist/*.mobile.*
|
||||
|
||||
./node_modules/.bin/electron-packager $SRC_DIR --asar --out=dist --executable-name=trilium --platform=darwin --arch=x64 --overwrite --icon=images/app-icons/mac/icon.icns
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PKG_DIR=dist/trilium-linux-x64-server
|
||||
NODE_VERSION=12.16.2
|
||||
NODE_VERSION=12.16.3
|
||||
|
||||
if [ "$1" != "DONTCOPY" ]
|
||||
then
|
||||
|
||||
@@ -19,7 +19,7 @@ cp bin/deps/win-x64/image/cjpeg.exe $SRC_DIR/node_modules/mozjpeg/vendor/
|
||||
cp bin/deps/win-x64/image/pngquant.exe $SRC_DIR/node_modules/pngquant-bin/vendor/
|
||||
cp bin/deps/win-x64/image/gifsicle.exe $SRC_DIR/node_modules/giflossy/vendor/
|
||||
|
||||
rm -r $SRC_DIR/src/public/dist/*.mobile.*
|
||||
rm -r $SRC_DIR/src/public/app-dist/*.mobile.*
|
||||
|
||||
./node_modules/.bin/electron-packager $SRC_DIR --asar --out=dist --executable-name=trilium --platform=win32 --arch=x64 --overwrite --icon=images/app-icons/win/icon.ico
|
||||
|
||||
@@ -31,6 +31,8 @@ mv "./dist/Trilium Notes-win32-x64" $BUILD_DIR
|
||||
# removing software WebGL binaries because they are pretty huge and not necessary
|
||||
rm -r $BUILD_DIR/swiftshader
|
||||
|
||||
cp bin/tpl/portable-trilium.bat $BUILD_DIR/
|
||||
|
||||
echo "Zipping windows x64 electron distribution..."
|
||||
VERSION=`jq -r ".version" package.json`
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ if [[ $# -eq 0 ]] ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
npm run webpack
|
||||
|
||||
DIR=$1
|
||||
|
||||
rm -rf $DIR
|
||||
@@ -25,11 +27,15 @@ cp -r electron.js $DIR/
|
||||
cp webpack-* $DIR/
|
||||
|
||||
# run in subshell (so we return to original dir)
|
||||
(cd $DIR && npm install --only=prod && npm run webpack)
|
||||
(cd $DIR && npm install --only=prod)
|
||||
|
||||
find $DIR/libraries -name "*.map" -type f -delete
|
||||
|
||||
rm -r $DIR/src/public/app
|
||||
|
||||
rm -r $DIR/node_modules/sqlite3/build
|
||||
rm -r $DIR/node_modules/sqlite3/deps
|
||||
|
||||
sed -i -e 's/app\/desktop.js/app-dist\/desktop.js/g' $DIR/src/views/desktop.ejs
|
||||
sed -i -e 's/app\/mobile.js/app-dist\/mobile.js/g' $DIR/src/views/mobile.ejs
|
||||
sed -i -e 's/app\/mobile.js/app-dist\/mobile.js/g' $DIR/src/views/mobile.ejs
|
||||
sed -i -e 's/app\/setup.js/app-dist\/setup.js/g' $DIR/src/views/setup.ejs
|
||||
|
||||
4
bin/tpl/portable-trilium.bat
Normal file
4
bin/tpl/portable-trilium.bat
Normal file
@@ -0,0 +1,4 @@
|
||||
SET DIR=%~dp0
|
||||
SET TRILIUM_DATA_DIR=%DIR%\trilium-data
|
||||
cd %DIR%
|
||||
start trilium.exe
|
||||
7
bin/tpl/portable-trilium.sh
Executable file
7
bin/tpl/portable-trilium.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
DIR=`dirname "$0"`
|
||||
export TRILIUM_DATA_DIR="$DIR/trilium-data"
|
||||
|
||||
"$DIR/trilium"
|
||||
|
||||
BIN
db/demo.zip
BIN
db/demo.zip
Binary file not shown.
@@ -879,7 +879,7 @@
|
||||
</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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
<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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
@@ -5483,7 +5483,7 @@ Typical use case is when new note has been created, we should wait until it is s
|
||||
</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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
<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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
@@ -507,7 +507,7 @@
|
||||
</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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
<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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
@@ -6813,7 +6813,7 @@ Cache is note instance scoped.
|
||||
</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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
<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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
@@ -79,7 +79,7 @@ export default Attribute;</code></pre>
|
||||
</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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
<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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
@@ -81,7 +81,7 @@ export default Branch;</code></pre>
|
||||
</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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
<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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
@@ -61,7 +61,7 @@ export default NoteComplement;</code></pre>
|
||||
</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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
<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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
@@ -493,7 +493,7 @@ export default NoteShort;</code></pre>
|
||||
</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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
<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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
@@ -95,6 +95,275 @@
|
||||
|
||||
|
||||
|
||||
|
||||
<h3 class="subsection-title">Methods</h3>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="decorateWidget"><span class="type-signature"></span>decorateWidget<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="description">
|
||||
for overriding
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="widgets_collapsible_widget.js.html">widgets/collapsible_widget.js</a>, <a href="widgets_collapsible_widget.js.html#line93">line 93</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="doRenderBody"><span class="type-signature">(async) </span>doRenderBody<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="description">
|
||||
for overriding
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="widgets_collapsible_widget.js.html">widgets/collapsible_widget.js</a>, <a href="widgets_collapsible_widget.js.html#line96">line 96</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="widgetCollapsedStateChangedEvent"><span class="type-signature"></span>widgetCollapsedStateChangedEvent<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="description">
|
||||
This event is used to synchronize collapsed state of all the tab-cached widgets since they are all rendered
|
||||
separately but should behave uniformly for the user.
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="widgets_collapsible_widget.js.html">widgets/collapsible_widget.js</a>, <a href="widgets_collapsible_widget.js.html#line86">line 86</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -333,7 +602,7 @@
|
||||
</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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
<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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
</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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
<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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
@@ -443,7 +443,7 @@ export default FrontendScriptApi;</code></pre>
|
||||
</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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
|
||||
<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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
151
docs/frontend_api/widgets_collapsible_widget.js.html
Normal file
151
docs/frontend_api/widgets_collapsible_widget.js.html
Normal file
@@ -0,0 +1,151 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: widgets/collapsible_widget.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: widgets/collapsible_widget.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>import TabAwareWidget from "./tab_aware_widget.js";
|
||||
import options from "../services/options.js";
|
||||
|
||||
const WIDGET_TPL = `
|
||||
<div class="card widget">
|
||||
<div class="card-header">
|
||||
<div>
|
||||
<button class="btn btn-sm widget-title" data-toggle="collapse" data-target="#[to be set]">
|
||||
Collapsible Group Item
|
||||
</button>
|
||||
|
||||
<a class="widget-help external no-arrow bx bx-info-circle"></a>
|
||||
</div>
|
||||
|
||||
<div class="widget-header-actions"></div>
|
||||
</div>
|
||||
|
||||
<div id="[to be set]" class="collapse body-wrapper" style="transition: none; ">
|
||||
<div class="card-body"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export default class CollapsibleWidget extends TabAwareWidget {
|
||||
get widgetTitle() { return "Untitled widget"; }
|
||||
|
||||
get headerActions() { return []; }
|
||||
|
||||
get help() { return {}; }
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(WIDGET_TPL);
|
||||
this.$widget.find('[data-target]').attr('data-target', "#" + this.componentId);
|
||||
|
||||
this.$bodyWrapper = this.$widget.find('.body-wrapper');
|
||||
this.$bodyWrapper.attr('id', this.componentId); // for toggle to work we need id
|
||||
|
||||
this.widgetName = this.constructor.name;
|
||||
|
||||
if (!options.is(this.widgetName + 'Collapsed')) {
|
||||
this.$bodyWrapper.collapse("show");
|
||||
}
|
||||
|
||||
// using immediate variants of the event so that the previous collapse is not caught
|
||||
this.$bodyWrapper.on('hide.bs.collapse', () => this.saveCollapsed(true));
|
||||
this.$bodyWrapper.on('show.bs.collapse', () => this.saveCollapsed(false));
|
||||
|
||||
this.$body = this.$bodyWrapper.find('.card-body');
|
||||
|
||||
this.$title = this.$widget.find('.widget-title');
|
||||
this.$title.text(this.widgetTitle);
|
||||
|
||||
this.$help = this.$widget.find('.widget-help');
|
||||
|
||||
if (this.help.title) {
|
||||
this.$help.attr("title", this.help.title);
|
||||
this.$help.attr("href", this.help.url || "javascript:");
|
||||
|
||||
if (!this.help.url) {
|
||||
this.$help.addClass('no-link');
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.$help.hide();
|
||||
}
|
||||
|
||||
this.$headerActions = this.$widget.find('.widget-header-actions');
|
||||
this.$headerActions.append(...this.headerActions);
|
||||
|
||||
this.initialized = this.doRenderBody();
|
||||
|
||||
this.decorateWidget();
|
||||
|
||||
return this.$widget;
|
||||
}
|
||||
|
||||
saveCollapsed(collapse) {
|
||||
options.save(this.widgetName + 'Collapsed', collapse.toString());
|
||||
|
||||
this.triggerEvent(`widgetCollapsedStateChanged`, {widgetName: this.widgetName, collapse});
|
||||
}
|
||||
|
||||
/**
|
||||
* This event is used to synchronize collapsed state of all the tab-cached widgets since they are all rendered
|
||||
* separately but should behave uniformly for the user.
|
||||
*/
|
||||
widgetCollapsedStateChangedEvent({widgetName, collapse}) {
|
||||
if (widgetName === this.widgetName) {
|
||||
this.$bodyWrapper.toggleClass('show', !collapse);
|
||||
}
|
||||
}
|
||||
|
||||
/** for overriding */
|
||||
decorateWidget() {}
|
||||
|
||||
/** for overriding */
|
||||
async doRenderBody() {}
|
||||
|
||||
isExpanded() {
|
||||
return this.$bodyWrapper.hasClass("show");
|
||||
}
|
||||
}</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="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.4</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +1,4 @@
|
||||
For bug reports, please mention **version of the application** and include **log files** from following location:
|
||||
For bug reports, **PLEASE mention version of Trilium you're using** and also include **log files** from following location:
|
||||
|
||||
* `/home/[user]/.local/share/trilium-data/log` for Linux
|
||||
* `C:\Users\[user]\AppData\Roaming\trilium-data\log` for Windows Vista and up
|
||||
|
||||
2
libraries/ckeditor/ckeditor.js
vendored
2
libraries/ckeditor/ckeditor.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2986,7 +2986,7 @@ var uniqueId = $.fn.extend( {
|
||||
self = this,
|
||||
wasExpanded = this.isExpanded();
|
||||
|
||||
_assert(this.isLazy(), "load() requires a lazy node");
|
||||
//_assert(this.isLazy(), "load() requires a lazy node");
|
||||
// _assert( forceReload || this.isUndefined(), "Pass forceReload=true to re-load a lazy node" );
|
||||
if (!forceReload && !this.isUndefined()) {
|
||||
return _getResolvedPromise(this);
|
||||
|
||||
904
package-lock.json
generated
904
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.41.3-beta",
|
||||
"version": "0.43.1",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@@ -16,28 +16,28 @@
|
||||
"start-server": "TRILIUM_ENV=dev node ./src/www",
|
||||
"start-electron": "TRILIUM_ENV=dev electron .",
|
||||
"build-backend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js",
|
||||
"build-frontend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js",
|
||||
"build-frontend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/collapsible_widget.js",
|
||||
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
|
||||
"webpack": "npx webpack -c webpack-desktop.config.js && npx webpack -c webpack-mobile.config.js && npx webpack -c webpack-setup.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"async-mutex": "0.2.1",
|
||||
"async-mutex": "0.2.2",
|
||||
"axios": "0.19.2",
|
||||
"body-parser": "1.19.0",
|
||||
"cls-hooked": "4.2.2",
|
||||
"commonmark": "0.29.1",
|
||||
"cookie-parser": "1.4.5",
|
||||
"csurf": "1.11.0",
|
||||
"dayjs": "1.8.24",
|
||||
"dayjs": "1.8.26",
|
||||
"debug": "4.1.1",
|
||||
"ejs": "3.0.2",
|
||||
"ejs": "3.1.2",
|
||||
"electron-debug": "3.0.1",
|
||||
"electron-dl": "3.0.0",
|
||||
"electron-find": "1.0.6",
|
||||
"electron-window-state": "5.0.3",
|
||||
"express": "4.17.1",
|
||||
"express-session": "1.17.0",
|
||||
"file-type": "14.1.4",
|
||||
"express-session": "1.17.1",
|
||||
"file-type": "14.3.0",
|
||||
"fs-extra": "9.0.0",
|
||||
"helmet": "3.22.0",
|
||||
"html": "1.0.0",
|
||||
@@ -51,10 +51,10 @@
|
||||
"imagemin-pngquant": "8.0.0",
|
||||
"ini": "1.3.5",
|
||||
"is-svg": "4.2.1",
|
||||
"jimp": "0.10.2",
|
||||
"mime-types": "2.1.26",
|
||||
"jimp": "0.10.3",
|
||||
"mime-types": "2.1.27",
|
||||
"multer": "1.4.2",
|
||||
"node-abi": "2.15.0",
|
||||
"node-abi": "2.16.0",
|
||||
"open": "7.0.3",
|
||||
"portscanner": "2.2.0",
|
||||
"rand-token": "1.0.1",
|
||||
@@ -73,18 +73,18 @@
|
||||
"turndown": "6.0.0",
|
||||
"turndown-plugin-gfm": "1.0.2",
|
||||
"unescape": "1.0.1",
|
||||
"ws": "7.2.3",
|
||||
"ws": "7.2.5",
|
||||
"yauzl": "^2.10.0",
|
||||
"yazl": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "9.0.0-beta.16",
|
||||
"electron-builder": "22.4.1",
|
||||
"electron": "9.0.5",
|
||||
"electron-builder": "22.6.0",
|
||||
"electron-packager": "14.2.1",
|
||||
"electron-rebuild": "1.10.1",
|
||||
"jsdoc": "3.6.4",
|
||||
"lorem-ipsum": "2.0.3",
|
||||
"webpack": "5.0.0-beta.14",
|
||||
"webpack": "5.0.0-beta.16",
|
||||
"webpack-cli": "4.0.0-beta.8"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -1,7 +1,24 @@
|
||||
const anonymizationService = require('./services/anonymization');
|
||||
const backupService = require('./services/backup');
|
||||
const sqlInit = require('./services/sql_init');
|
||||
require('./entities/entity_constructor');
|
||||
|
||||
anonymizationService.anonymize().then(filePath => {
|
||||
console.log("Anonymized file has been saved to:", filePath);
|
||||
sqlInit.dbReady.then(async () => {
|
||||
try {
|
||||
console.log("Starting anonymization...");
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
const resp = await backupService.anonymize();
|
||||
|
||||
if (resp.success) {
|
||||
console.log("Anonymized file has been saved to: " + resp.anonymizedFilePath);
|
||||
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log("Anonymization failed.");
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e.message, e.stack);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
13
src/app.js
13
src/app.js
@@ -28,17 +28,6 @@ app.use((req, res, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
app.use((req, res, next) => {
|
||||
cls.namespace.bindEmitter(req);
|
||||
cls.namespace.bindEmitter(res);
|
||||
|
||||
cls.init(() => {
|
||||
cls.namespace.set("Hi");
|
||||
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
app.use(bodyParser.json({limit: '500mb'}));
|
||||
app.use(bodyParser.urlencoded({extended: false}));
|
||||
app.use(cookieParser());
|
||||
@@ -120,4 +109,4 @@ require('./services/scheduler');
|
||||
module.exports = {
|
||||
app,
|
||||
sessionParser
|
||||
};
|
||||
};
|
||||
|
||||
@@ -105,8 +105,7 @@ class Attribute extends Entity {
|
||||
|
||||
// cannot be static!
|
||||
updatePojo(pojo) {
|
||||
delete pojo.isOwned;
|
||||
delete pojo.__note;
|
||||
delete pojo.__note; // FIXME: probably note necessary anymore
|
||||
}
|
||||
|
||||
createClone(type, name, value) {
|
||||
@@ -124,4 +123,4 @@ class Attribute extends Entity {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Attribute;
|
||||
module.exports = Attribute;
|
||||
|
||||
@@ -411,10 +411,6 @@ class Note extends Entity {
|
||||
}
|
||||
});
|
||||
|
||||
for (const attr of filteredAttributes) {
|
||||
attr.isOwned = attr.noteId === this.noteId;
|
||||
}
|
||||
|
||||
this.__attributeCache = filteredAttributes;
|
||||
}
|
||||
|
||||
@@ -545,12 +541,13 @@ class Note extends Entity {
|
||||
/**
|
||||
* @return {Promise<Attribute>}
|
||||
*/
|
||||
async addAttribute(type, name, value = "") {
|
||||
async addAttribute(type, name, value = "", isInheritable = false) {
|
||||
const attr = new Attribute({
|
||||
noteId: this.noteId,
|
||||
type: type,
|
||||
name: name,
|
||||
value: value
|
||||
value: value,
|
||||
isInheritable: isInheritable
|
||||
});
|
||||
|
||||
await attr.save();
|
||||
@@ -560,12 +557,12 @@ class Note extends Entity {
|
||||
return attr;
|
||||
}
|
||||
|
||||
async addLabel(name, value = "") {
|
||||
return await this.addAttribute(LABEL, name, value);
|
||||
async addLabel(name, value = "", isInheritable = false) {
|
||||
return await this.addAttribute(LABEL, name, value, isInheritable);
|
||||
}
|
||||
|
||||
async addRelation(name, targetNoteId) {
|
||||
return await this.addAttribute(RELATION, name, targetNoteId);
|
||||
async addRelation(name, targetNoteId, isInheritable = false) {
|
||||
return await this.addAttribute(RELATION, name, targetNoteId, isInheritable);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -946,4 +943,4 @@ class Note extends Entity {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Note;
|
||||
module.exports = Note;
|
||||
|
||||
@@ -5,8 +5,10 @@ import bundleService from "./services/bundle.js";
|
||||
import noteAutocompleteService from './services/note_autocomplete.js';
|
||||
import macInit from './services/mac_init.js';
|
||||
import contextMenu from "./services/context_menu.js";
|
||||
import DesktopLayout from "./widgets/desktop_layout.js";
|
||||
import DesktopMainWindowLayout from "./layouts/desktop_main_window_layout.js";
|
||||
import glob from "./services/glob.js";
|
||||
import DesktopExtraWindowLayout from "./layouts/desktop_extra_window_layout.js";
|
||||
import zoomService from './services/zoom.js';
|
||||
|
||||
glob.setupGlobs();
|
||||
|
||||
@@ -23,9 +25,11 @@ $('[data-toggle="tooltip"]').tooltip({
|
||||
macInit.init();
|
||||
|
||||
bundleService.getWidgetBundlesByParent().then(widgetBundles => {
|
||||
const desktopLayout = new DesktopLayout(widgetBundles);
|
||||
const layout = window.glob.isMainWindow
|
||||
? new DesktopMainWindowLayout(widgetBundles)
|
||||
: new DesktopExtraWindowLayout(widgetBundles);
|
||||
|
||||
appContext.setLayout(desktopLayout);
|
||||
appContext.setLayout(layout);
|
||||
appContext.start();
|
||||
});
|
||||
|
||||
@@ -82,7 +86,7 @@ if (utils.isElectron()) {
|
||||
});
|
||||
}
|
||||
|
||||
if (params.linkURL.length !== 0 && params.mediaType === 'none') {
|
||||
if (!["", "javascript:", "about:blank#blocked"].includes(params.linkURL) && params.mediaType === 'none') {
|
||||
items.push({
|
||||
title: `Copy link`,
|
||||
uiIcon: "copy",
|
||||
@@ -130,9 +134,11 @@ if (utils.isElectron()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const zoomLevel = zoomService.getCurrentZoom();
|
||||
|
||||
contextMenu.show({
|
||||
x: params.x,
|
||||
y: params.y,
|
||||
x: params.x / zoomLevel,
|
||||
y: params.y / zoomLevel,
|
||||
items,
|
||||
selectMenuItemHandler: ({command, spellingSuggestion}) => {
|
||||
if (command === 'replaceMisspelling') {
|
||||
@@ -141,4 +147,4 @@ if (utils.isElectron()) {
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ function AttributesModel() {
|
||||
});
|
||||
};
|
||||
|
||||
async function showAttributes(attributes) {
|
||||
const ownedAttributes = attributes.filter(attr => attr.isOwned);
|
||||
async function showAttributes(noteId, attributes) {
|
||||
const ownedAttributes = attributes.filter(attr => attr.noteId === noteId);
|
||||
|
||||
for (const attr of ownedAttributes) {
|
||||
attr.labelValue = attr.type === 'label' ? attr.value : '';
|
||||
@@ -86,7 +86,7 @@ function AttributesModel() {
|
||||
|
||||
addLastEmptyRow();
|
||||
|
||||
const inheritedAttributes = attributes.filter(attr => !attr.isOwned);
|
||||
const inheritedAttributes = attributes.filter(attr => attr.noteId !== noteId);
|
||||
|
||||
self.inheritedAttributes(inheritedAttributes);
|
||||
}
|
||||
@@ -96,7 +96,7 @@ function AttributesModel() {
|
||||
|
||||
const attributes = await server.get('notes/' + noteId + '/attributes');
|
||||
|
||||
await showAttributes(attributes);
|
||||
await showAttributes(noteId, attributes);
|
||||
|
||||
// attribute might not be rendered immediatelly so could not focus
|
||||
setTimeout(() => $(".attribute-type-select:last").trigger('focus'), 1000);
|
||||
@@ -152,10 +152,10 @@ function AttributesModel() {
|
||||
attr.value = treeService.getNoteIdFromNotePath(attr.selectedPath);
|
||||
}
|
||||
else if (attr.type === 'label-definition') {
|
||||
attr.value = attr.labelDefinition;
|
||||
attr.value = JSON.stringify(attr.labelDefinition);
|
||||
}
|
||||
else if (attr.type === 'relation-definition') {
|
||||
attr.value = attr.relationDefinition;
|
||||
attr.value = JSON.stringify(attr.relationDefinition);
|
||||
}
|
||||
|
||||
delete attr.labelValue;
|
||||
@@ -166,7 +166,7 @@ function AttributesModel() {
|
||||
|
||||
const attributes = await server.put('notes/' + noteId + '/attributes', attributesToSave);
|
||||
|
||||
await showAttributes(attributes);
|
||||
await showAttributes(noteId, attributes);
|
||||
|
||||
toastService.showMessage("Attributes have been saved.");
|
||||
};
|
||||
@@ -311,4 +311,4 @@ $dialog.on('focus', '.label-value', function (e) {
|
||||
$el: $(this),
|
||||
open: true
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,13 +39,14 @@ export async function showDialog(noteIds) {
|
||||
}
|
||||
|
||||
async function cloneNotesTo(notePath) {
|
||||
const targetNoteId = treeService.getNoteIdFromNotePath(notePath);
|
||||
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(notePath);
|
||||
const targetBranchId = await treeCache.getBranchId(parentNoteId, noteId);
|
||||
|
||||
for (const cloneNoteId of clonedNoteIds) {
|
||||
await branchService.cloneNoteTo(cloneNoteId, targetNoteId, $clonePrefix.val());
|
||||
await branchService.cloneNoteTo(cloneNoteId, targetBranchId, $clonePrefix.val());
|
||||
|
||||
const clonedNote = await treeCache.getNote(cloneNoteId);
|
||||
const targetNote = await treeCache.getNote(targetNoteId);
|
||||
const targetNote = await treeCache.getBranch(targetBranchId).getNote();
|
||||
|
||||
toastService.showMessage(`Note "${clonedNote.title}" has been cloned into ${targetNote.title}`);
|
||||
}
|
||||
@@ -64,4 +65,4 @@ $form.on('submit', () => {
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,10 +32,11 @@ export async function showDialog(branchIds) {
|
||||
noteAutocompleteService.showRecentNotes($noteAutoComplete);
|
||||
}
|
||||
|
||||
async function moveNotesTo(parentNoteId) {
|
||||
await branchService.moveToParentNote(movedBranchIds, parentNoteId);
|
||||
async function moveNotesTo(parentBranchId) {
|
||||
await branchService.moveToParentNote(movedBranchIds, parentBranchId);
|
||||
|
||||
const parentNote = await treeCache.getNote(parentNoteId);
|
||||
const parentBranch = treeCache.getBranch(parentBranchId);
|
||||
const parentNote = await parentBranch.getNote();
|
||||
|
||||
toastService.showMessage(`Selected notes have been moved into ${parentNote.title}`);
|
||||
}
|
||||
@@ -46,13 +47,12 @@ $form.on('submit', () => {
|
||||
if (notePath) {
|
||||
$dialog.modal('hide');
|
||||
|
||||
const noteId = treeService.getNoteIdFromNotePath(notePath);
|
||||
|
||||
moveNotesTo(noteId);
|
||||
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(notePath);
|
||||
treeCache.getBranchId(parentNoteId, noteId).then(branchId => moveNotesTo(branchId));
|
||||
}
|
||||
else {
|
||||
console.error("No path to move to.");
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,14 +37,18 @@ export async function showNoteRevisionsDialog(noteId, noteRevisionId) {
|
||||
async function loadNoteRevisions(noteId, noteRevId) {
|
||||
$list.empty();
|
||||
$content.empty();
|
||||
$titleButtons.empty();
|
||||
|
||||
note = appContext.tabManager.getActiveTabNote();
|
||||
revisionItems = await server.get(`notes/${noteId}/revisions`);
|
||||
|
||||
for (const item of revisionItems) {
|
||||
$list.append($('<a class="dropdown-item" tabindex="0">')
|
||||
.text(item.dateLastEdited.substr(0, 16) + ` (${item.contentLength} bytes)`)
|
||||
.attr('data-note-revision-id', item.noteRevisionId));
|
||||
$list.append(
|
||||
$('<a class="dropdown-item" tabindex="0">')
|
||||
.text(item.dateLastEdited.substr(0, 16) + ` (${item.contentLength} bytes)`)
|
||||
.attr('data-note-revision-id', item.noteRevisionId)
|
||||
.attr('title', 'This revision was last edited on ' + item.dateLastEdited)
|
||||
);
|
||||
}
|
||||
|
||||
$listDropdown.dropdown('show');
|
||||
@@ -59,6 +63,8 @@ async function loadNoteRevisions(noteId, noteRevId) {
|
||||
$title.text("No revisions for this note yet...");
|
||||
noteRevisionId = null;
|
||||
}
|
||||
|
||||
$eraseAllRevisionsButton.toggle(revisionItems.length > 0);
|
||||
}
|
||||
|
||||
$dialog.on('shown.bs.modal', () => {
|
||||
@@ -76,6 +82,21 @@ async function setContentPane() {
|
||||
|
||||
$title.html(revisionItem.title);
|
||||
|
||||
const $restoreRevisionButton = $('<button class="btn btn-sm" type="button">Restore this revision</button>');
|
||||
|
||||
$restoreRevisionButton.on('click', async () => {
|
||||
const confirmDialog = await import('../dialogs/confirm.js');
|
||||
const text = 'Do you want to restore this revision? This will overwrite current title/content of the note with this revision.';
|
||||
|
||||
if (await confirmDialog.confirm(text)) {
|
||||
await server.put(`notes/${revisionItem.noteId}/restore-revision/${revisionItem.noteRevisionId}`);
|
||||
|
||||
$dialog.modal('hide');
|
||||
|
||||
toastService.showMessage('Note revision has been restored.');
|
||||
}
|
||||
});
|
||||
|
||||
const $eraseRevisionButton = $('<button class="btn btn-sm" type="button">Delete this revision</button>');
|
||||
|
||||
$eraseRevisionButton.on('click', async () => {
|
||||
@@ -92,6 +113,8 @@ async function setContentPane() {
|
||||
});
|
||||
|
||||
$titleButtons
|
||||
.append($restoreRevisionButton)
|
||||
.append(' ')
|
||||
.append($eraseRevisionButton)
|
||||
.append(' ');
|
||||
|
||||
|
||||
@@ -17,12 +17,18 @@ const TPL = `
|
||||
|
||||
<button id="find-and-fix-consistency-issues-button" class="btn">Find and fix consistency issues</button><br/><br/>
|
||||
|
||||
<h4>Debugging</h4>
|
||||
<h4>Anonymize database</h4>
|
||||
|
||||
<p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and some non-sensitive metadata)
|
||||
for sharing online for debugging purposes without fear of leaking your personal data.</p>
|
||||
|
||||
<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>
|
||||
<h4>Backup database</h4>
|
||||
|
||||
<p>Trilium has automatic backup (daily, weekly, monthly), but you can also trigger backup manually here.</p>
|
||||
|
||||
<button id="backup-database-button" class="btn">Backup database now</button><br/><br/>
|
||||
|
||||
<h4>Vacuum database</h4>
|
||||
|
||||
@@ -37,6 +43,7 @@ export default class AdvancedOptions {
|
||||
this.$forceFullSyncButton = $("#force-full-sync-button");
|
||||
this.$fillSyncRowsButton = $("#fill-sync-rows-button");
|
||||
this.$anonymizeButton = $("#anonymize-button");
|
||||
this.$backupDatabaseButton = $("#backup-database-button");
|
||||
this.$vacuumDatabaseButton = $("#vacuum-database-button");
|
||||
this.$findAndFixConsistencyIssuesButton = $("#find-and-fix-consistency-issues-button");
|
||||
|
||||
@@ -53,21 +60,32 @@ export default class AdvancedOptions {
|
||||
});
|
||||
|
||||
this.$anonymizeButton.on('click', async () => {
|
||||
await server.post('anonymization/anonymize');
|
||||
const resp = await server.post('database/anonymize');
|
||||
|
||||
toastService.showMessage("Created anonymized database");
|
||||
if (!resp.success) {
|
||||
toastService.showError("Could not create anonymized database, check backend logs for details");
|
||||
}
|
||||
else {
|
||||
toastService.showMessage(`Created anonymized database in ${resp.anonymizedFilePath}`, 10000);
|
||||
}
|
||||
});
|
||||
|
||||
this.$backupDatabaseButton.on('click', async () => {
|
||||
const {backupFile} = await server.post('database/backup-database');
|
||||
|
||||
toastService.showMessage("Database has been backed up to " + backupFile, 10000);
|
||||
});
|
||||
|
||||
this.$vacuumDatabaseButton.on('click', async () => {
|
||||
await server.post('cleanup/vacuum-database');
|
||||
await server.post('database/vacuum-database');
|
||||
|
||||
toastService.showMessage("Database has been vacuumed");
|
||||
});
|
||||
|
||||
this.$findAndFixConsistencyIssuesButton.on('click', async () => {
|
||||
await server.post('cleanup/find-and-fix-consistency-issues');
|
||||
await server.post('database/find-and-fix-consistency-issues');
|
||||
|
||||
toastService.showMessage("Consistency issues should be fixed.");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import optionsService from "../../services/options.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import server from "../../services/server.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
|
||||
@@ -20,7 +20,9 @@ const TPL = `
|
||||
<input type="text" class="form-control" id="spell-check-language-code" placeholder="for example "en-US", "de-AT"">
|
||||
</div>
|
||||
|
||||
<p>Multiple languages can be separated by comman. Changes to the spell check options will take effect after application restart.</p>
|
||||
<p>Multiple languages can be separated by comma, e.g. <code>en-US, de-DE, cs</code>. Changes to the spell check options will take effect after application restart.</p>
|
||||
|
||||
<p><strong>Available language codes: </strong> <span id="available-language-codes"></span></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -95,6 +97,14 @@ export default class ProtectedSessionOptions {
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$availableLanguageCodes = $("#available-language-codes");
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const {webContents} = utils.dynamicRequire('electron').remote.getCurrentWindow();
|
||||
|
||||
this.$availableLanguageCodes.text(webContents.session.availableSpellCheckerLanguages.join(', '));
|
||||
}
|
||||
|
||||
this.$eraseNotesAfterTimeInSeconds = $("#erase-notes-after-time-in-seconds");
|
||||
|
||||
this.$eraseNotesAfterTimeInSeconds.on('change', () => {
|
||||
|
||||
@@ -16,26 +16,26 @@ export async function showDialog(ancestorNoteId) {
|
||||
ancestorNoteId = hoistedNoteService.getHoistedNoteId();
|
||||
}
|
||||
|
||||
const result = await server.get('recent-changes/' + ancestorNoteId);
|
||||
const recentChangesRows = await server.get('recent-changes/' + ancestorNoteId);
|
||||
|
||||
// preload all notes into cache
|
||||
await treeCache.getNotes(result.map(r => r.noteId), true);
|
||||
await treeCache.getNotes(recentChangesRows.map(r => r.noteId), true);
|
||||
|
||||
$content.empty();
|
||||
|
||||
if (result.length === 0) {
|
||||
if (recentChangesRows.length === 0) {
|
||||
$content.append("No changes yet ...");
|
||||
}
|
||||
|
||||
const groupedByDate = groupByDate(result);
|
||||
const groupedByDate = groupByDate(recentChangesRows);
|
||||
|
||||
for (const [dateDay, dayChanges] of groupedByDate) {
|
||||
const $changesList = $('<ul>');
|
||||
|
||||
const dayEl = $('<div>').append($('<b>').html(utils.formatDate(dateDay))).append($changesList);
|
||||
const dayEl = $('<div>').append($('<b>').text(dateDay)).append($changesList);
|
||||
|
||||
for (const change of dayChanges) {
|
||||
const formattedTime = utils.formatTime(utils.parseDate(change.date));
|
||||
const formattedTime = change.date.substr(11, 5);
|
||||
|
||||
let $noteLink;
|
||||
|
||||
@@ -82,7 +82,12 @@ export async function showDialog(ancestorNoteId) {
|
||||
}
|
||||
|
||||
$changesList.append($('<li>')
|
||||
.append(formattedTime + ' - ')
|
||||
.append(
|
||||
$("<span>")
|
||||
.text(formattedTime)
|
||||
.attr("title", change.date)
|
||||
)
|
||||
.append(' - ')
|
||||
.append($noteLink));
|
||||
}
|
||||
|
||||
@@ -90,25 +95,11 @@ export async function showDialog(ancestorNoteId) {
|
||||
}
|
||||
}
|
||||
|
||||
function groupByDate(result) {
|
||||
function groupByDate(rows) {
|
||||
const groupedByDate = new Map();
|
||||
const dayCache = {};
|
||||
|
||||
for (const row of result) {
|
||||
let dateDay = utils.parseDate(row.date);
|
||||
dateDay.setHours(0);
|
||||
dateDay.setMinutes(0);
|
||||
dateDay.setSeconds(0);
|
||||
dateDay.setMilliseconds(0);
|
||||
|
||||
// this stupidity is to make sure that we always use the same day object because Map uses only
|
||||
// reference equality
|
||||
if (dayCache[dateDay]) {
|
||||
dateDay = dayCache[dateDay];
|
||||
}
|
||||
else {
|
||||
dayCache[dateDay] = dateDay;
|
||||
}
|
||||
for (const row of rows) {
|
||||
const dateDay = row.date.substr(0, 10);
|
||||
|
||||
if (!groupedByDate.has(dateDay)) {
|
||||
groupedByDate.set(dateDay, []);
|
||||
|
||||
@@ -23,8 +23,12 @@ class Attribute {
|
||||
}
|
||||
|
||||
/** @returns {NoteShort} */
|
||||
async getNote() {
|
||||
return await this.treeCache.getNote(this.noteId);
|
||||
getNote() {
|
||||
return this.treeCache.notes[this.noteId];
|
||||
}
|
||||
|
||||
get targetNoteId() { // alias
|
||||
return this.type === 'relation' ? this.value : undefined;
|
||||
}
|
||||
|
||||
get jsonValue() {
|
||||
@@ -39,6 +43,34 @@ class Attribute {
|
||||
get toString() {
|
||||
return `Attribute(attributeId=${this.attributeId}, type=${this.type}, name=${this.name}, value=${this.value})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} - returns true if this attribute has the potential to influence the note in the argument.
|
||||
* That can happen in multiple ways:
|
||||
* 1. attribute is owned by the note
|
||||
* 2. attribute is owned by the template of the note
|
||||
* 3. attribute is owned by some note's ancestor and is inheritable
|
||||
*/
|
||||
isAffecting(affectedNote) {
|
||||
const attrNote = this.getNote();
|
||||
const owningNotes = [affectedNote, ...affectedNote.getTemplateNotes()];
|
||||
|
||||
for (const owningNote of owningNotes) {
|
||||
if (owningNote.noteId === attrNote.noteId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isInheritable) {
|
||||
for (const owningNote of owningNotes) {
|
||||
if (owningNote.hasAncestor(attrNote)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default Attribute;
|
||||
export default Attribute;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import server from '../services/server.js';
|
||||
import Attribute from './attribute.js';
|
||||
import noteAttributeCache from "../services/note_attribute_cache.js";
|
||||
|
||||
const LABEL = 'label';
|
||||
const LABEL_DEFINITION = 'label-definition';
|
||||
@@ -7,6 +8,8 @@ const RELATION = 'relation';
|
||||
const RELATION_DEFINITION = 'relation-definition';
|
||||
|
||||
/**
|
||||
* FIXME: since there's no "full note" anymore we can rename this to Note
|
||||
*
|
||||
* This note's representation is used in note tree and is kept in TreeCache.
|
||||
*/
|
||||
class NoteShort {
|
||||
@@ -156,9 +159,9 @@ class NoteShort {
|
||||
getOwnedAttributes(type, name) {
|
||||
const attrs = this.attributes
|
||||
.map(attributeId => this.treeCache.attributes[attributeId])
|
||||
.filter(attr => !!attr);
|
||||
.filter(Boolean); // filter out nulls;
|
||||
|
||||
return this.__filterAttrs(attrs, type, name)
|
||||
return this.__filterAttrs(attrs, type, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,48 +170,62 @@ class NoteShort {
|
||||
* @returns {Attribute[]} all note's attributes, including inherited ones
|
||||
*/
|
||||
getAttributes(type, name) {
|
||||
const ownedAttributes = this.getOwnedAttributes();
|
||||
return this.__filterAttrs(this.__getCachedAttributes([]), type, name);
|
||||
}
|
||||
|
||||
const attrArrs = [
|
||||
ownedAttributes
|
||||
];
|
||||
|
||||
for (const templateAttr of ownedAttributes.filter(oa => oa.type === 'relation' && oa.name === 'template')) {
|
||||
const templateNote = this.treeCache.getNoteFromCache(templateAttr.value);
|
||||
|
||||
if (templateNote) {
|
||||
attrArrs.push(templateNote.getAttributes());
|
||||
}
|
||||
__getCachedAttributes(path) {
|
||||
// notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates
|
||||
// when template instance is a parent of template itself
|
||||
if (path.includes(this.noteId)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this.noteId !== 'root') {
|
||||
for (const parentNote of this.getParentNotes()) {
|
||||
// these virtual parent-child relationships are also loaded into frontend tree cache
|
||||
if (parentNote.type !== 'search') {
|
||||
attrArrs.push(parentNote.getInheritableAttributes());
|
||||
if (!(this.noteId in noteAttributeCache)) {
|
||||
const ownedAttributes = this.getOwnedAttributes();
|
||||
|
||||
const attrArrs = [
|
||||
ownedAttributes
|
||||
];
|
||||
|
||||
const newPath = [...path, this.noteId];
|
||||
|
||||
for (const templateAttr of ownedAttributes.filter(oa => oa.type === 'relation' && oa.name === 'template')) {
|
||||
const templateNote = this.treeCache.notes[templateAttr.value];
|
||||
|
||||
if (templateNote && templateNote.noteId !== this.noteId) {
|
||||
attrArrs.push(templateNote.__getCachedAttributes(newPath));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.noteId !== 'root') {
|
||||
for (const parentNote of this.getParentNotes()) {
|
||||
// these virtual parent-child relationships are also loaded into frontend tree cache
|
||||
if (parentNote.type !== 'search') {
|
||||
attrArrs.push(parentNote.__getInheritableAttributes(newPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
noteAttributeCache.attributes[this.noteId] = attrArrs.flat();
|
||||
}
|
||||
|
||||
const attributes = attrArrs.flat();
|
||||
|
||||
return this.__filterAttrs(attributes, type, name);
|
||||
return noteAttributeCache.attributes[this.noteId];
|
||||
}
|
||||
|
||||
__filterAttrs(attributes, type, name) {
|
||||
if (type && name) {
|
||||
if (!type && !name) {
|
||||
return attributes;
|
||||
} else if (type && name) {
|
||||
return attributes.filter(attr => attr.type === type && attr.name === name);
|
||||
} else if (type) {
|
||||
return attributes.filter(attr => attr.type === type);
|
||||
} else if (name) {
|
||||
return attributes.filter(attr => attr.name === name);
|
||||
} else {
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
|
||||
getInheritableAttributes() {
|
||||
const attrs = this.getAttributes();
|
||||
__getInheritableAttributes(path) {
|
||||
const attrs = this.__getCachedAttributes(path);
|
||||
|
||||
return attrs.filter(attr => attr.isInheritable);
|
||||
}
|
||||
@@ -420,6 +437,35 @@ class NoteShort {
|
||||
return targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {NoteShort[]}
|
||||
*/
|
||||
getTemplateNotes() {
|
||||
const relations = this.getRelations('template');
|
||||
|
||||
return relations.map(rel => this.treeCache.notes[rel.value]);
|
||||
}
|
||||
|
||||
hasAncestor(ancestorNote) {
|
||||
if (this.noteId === ancestorNote.noteId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const templateNote of this.getTemplateNotes()) {
|
||||
if (templateNote.hasAncestor(ancestorNote)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const parentNote of this.getParentNotes()) {
|
||||
if (parentNote.hasAncestor(ancestorNote)) {console.log(parentNote);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear note's attributes cache to force fresh reload for next attribute request.
|
||||
* Cache is note instance scoped.
|
||||
@@ -438,6 +484,15 @@ class NoteShort {
|
||||
.map(attributeId => this.treeCache.attributes[attributeId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return note complement which is most importantly note's content
|
||||
*
|
||||
* @return {Promise<NoteComplement>}
|
||||
*/
|
||||
async getNoteComplement() {
|
||||
return await this.treeCache.getNoteComplement(this.noteId);
|
||||
}
|
||||
|
||||
get toString() {
|
||||
return `Note(noteId=${this.noteId}, title=${this.title})`;
|
||||
}
|
||||
@@ -455,4 +510,4 @@ class NoteShort {
|
||||
}
|
||||
}
|
||||
|
||||
export default NoteShort;
|
||||
export default NoteShort;
|
||||
|
||||
46
src/public/app/layouts/desktop_extra_window_layout.js
Normal file
46
src/public/app/layouts/desktop_extra_window_layout.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import FlexContainer from "../widgets/flex_container.js";
|
||||
import GlobalMenuWidget from "../widgets/global_menu.js";
|
||||
import TabRowWidget from "../widgets/tab_row.js";
|
||||
import TitleBarButtonsWidget from "../widgets/title_bar_buttons.js";
|
||||
import NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import TabCachingWidget from "../widgets/tab_caching_widget.js";
|
||||
import NoteTitleWidget from "../widgets/note_title.js";
|
||||
import RunScriptButtonsWidget from "../widgets/run_script_buttons.js";
|
||||
import NoteTypeWidget from "../widgets/note_type.js";
|
||||
import NoteActionsWidget from "../widgets/note_actions.js";
|
||||
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
||||
import NoteDetailWidget from "../widgets/note_detail.js";
|
||||
|
||||
export default class DesktopExtraWindowLayout {
|
||||
constructor(customWidgets) {
|
||||
this.customWidgets = customWidgets;
|
||||
}
|
||||
|
||||
getRootWidget(appContext) {
|
||||
appContext.mainTreeWidget = new NoteTreeWidget();
|
||||
|
||||
return new FlexContainer('column')
|
||||
.setParent(appContext)
|
||||
.id('root-widget')
|
||||
.css('height', '100vh')
|
||||
.child(new FlexContainer('row')
|
||||
.child(new GlobalMenuWidget())
|
||||
.child(new TabRowWidget())
|
||||
.child(new TitleBarButtonsWidget()))
|
||||
.child(new FlexContainer('row')
|
||||
.collapsible()
|
||||
.child(new FlexContainer('column').id('center-pane').css('flex-grow', '1')
|
||||
.child(new FlexContainer('row').class('title-row')
|
||||
.cssBlock('.title-row > * { margin: 5px; }')
|
||||
.child(new NoteTitleWidget())
|
||||
.child(new RunScriptButtonsWidget().hideInZenMode())
|
||||
.child(new NoteTypeWidget().hideInZenMode())
|
||||
.child(new NoteActionsWidget().hideInZenMode())
|
||||
)
|
||||
.child(new TabCachingWidget(() => new PromotedAttributesWidget()))
|
||||
.child(new TabCachingWidget(() => new NoteDetailWidget()))
|
||||
.child(...this.customWidgets.get('center-pane'))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,30 @@
|
||||
import FlexContainer from "./flex_container.js";
|
||||
import GlobalMenuWidget from "./global_menu.js";
|
||||
import TabRowWidget from "./tab_row.js";
|
||||
import TitleBarButtonsWidget from "./title_bar_buttons.js";
|
||||
import StandardTopWidget from "./standard_top_widget.js";
|
||||
import SidePaneContainer from "./side_pane_container.js";
|
||||
import GlobalButtonsWidget from "./global_buttons.js";
|
||||
import SearchBoxWidget from "./search_box.js";
|
||||
import SearchResultsWidget from "./search_results.js";
|
||||
import NoteTreeWidget from "./note_tree.js";
|
||||
import TabCachingWidget from "./tab_caching_widget.js";
|
||||
import NotePathsWidget from "./note_paths.js";
|
||||
import NoteTitleWidget from "./note_title.js";
|
||||
import RunScriptButtonsWidget from "./run_script_buttons.js";
|
||||
import ProtectedNoteSwitchWidget from "./protected_note_switch.js";
|
||||
import NoteTypeWidget from "./note_type.js";
|
||||
import NoteActionsWidget from "./note_actions.js";
|
||||
import PromotedAttributesWidget from "./promoted_attributes.js";
|
||||
import NoteDetailWidget from "./note_detail.js";
|
||||
import NoteInfoWidget from "./note_info.js";
|
||||
import CalendarWidget from "./calendar.js";
|
||||
import AttributesWidget from "./attributes.js";
|
||||
import LinkMapWidget from "./link_map.js";
|
||||
import NoteRevisionsWidget from "./note_revisions.js";
|
||||
import SimilarNotesWidget from "./similar_notes.js";
|
||||
import WhatLinksHereWidget from "./what_links_here.js";
|
||||
import SidePaneToggles from "./side_pane_toggles.js";
|
||||
import appContext from "../services/app_context.js";
|
||||
import FlexContainer from "../widgets/flex_container.js";
|
||||
import GlobalMenuWidget from "../widgets/global_menu.js";
|
||||
import TabRowWidget from "../widgets/tab_row.js";
|
||||
import TitleBarButtonsWidget from "../widgets/title_bar_buttons.js";
|
||||
import StandardTopWidget from "../widgets/standard_top_widget.js";
|
||||
import SidePaneContainer from "../widgets/side_pane_container.js";
|
||||
import GlobalButtonsWidget from "../widgets/global_buttons.js";
|
||||
import SearchBoxWidget from "../widgets/search_box.js";
|
||||
import SearchResultsWidget from "../widgets/search_results.js";
|
||||
import NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import TabCachingWidget from "../widgets/tab_caching_widget.js";
|
||||
import NotePathsWidget from "../widgets/note_paths.js";
|
||||
import NoteTitleWidget from "../widgets/note_title.js";
|
||||
import RunScriptButtonsWidget from "../widgets/run_script_buttons.js";
|
||||
import NoteTypeWidget from "../widgets/note_type.js";
|
||||
import NoteActionsWidget from "../widgets/note_actions.js";
|
||||
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
||||
import NoteDetailWidget from "../widgets/note_detail.js";
|
||||
import NoteInfoWidget from "../widgets/collapsible_widgets/note_info.js";
|
||||
import CalendarWidget from "../widgets/collapsible_widgets/calendar.js";
|
||||
import AttributesWidget from "../widgets/collapsible_widgets/attributes.js";
|
||||
import LinkMapWidget from "../widgets/collapsible_widgets/link_map.js";
|
||||
import NoteRevisionsWidget from "../widgets/collapsible_widgets/note_revisions.js";
|
||||
import SimilarNotesWidget from "../widgets/collapsible_widgets/similar_notes.js";
|
||||
import WhatLinksHereWidget from "../widgets/collapsible_widgets/what_links_here.js";
|
||||
import SidePaneToggles from "../widgets/side_pane_toggles.js";
|
||||
import EditedNotesWidget from "../widgets/collapsible_widgets/edited_notes.js";
|
||||
|
||||
const RIGHT_PANE_CSS = `
|
||||
<style>
|
||||
@@ -98,13 +97,13 @@ const RIGHT_PANE_CSS = `
|
||||
}
|
||||
</style>`;
|
||||
|
||||
export default class DesktopLayout {
|
||||
export default class DesktopMainWindowLayout {
|
||||
constructor(customWidgets) {
|
||||
this.customWidgets = customWidgets;
|
||||
}
|
||||
|
||||
getRootWidget(appContext) {
|
||||
appContext.mainTreeWidget = new NoteTreeWidget();
|
||||
appContext.mainTreeWidget = new NoteTreeWidget("main");
|
||||
|
||||
return new FlexContainer('column')
|
||||
.setParent(appContext)
|
||||
@@ -118,6 +117,7 @@ export default class DesktopLayout {
|
||||
.hideInZenMode())
|
||||
.child(new FlexContainer('row')
|
||||
.collapsible()
|
||||
.filling()
|
||||
.child(new SidePaneContainer('left')
|
||||
.hideInZenMode()
|
||||
.child(new GlobalButtonsWidget())
|
||||
@@ -132,7 +132,6 @@ export default class DesktopLayout {
|
||||
.cssBlock('.title-row > * { margin: 5px; }')
|
||||
.child(new NoteTitleWidget())
|
||||
.child(new RunScriptButtonsWidget().hideInZenMode())
|
||||
.child(new ProtectedNoteSwitchWidget().hideInZenMode())
|
||||
.child(new NoteTypeWidget().hideInZenMode())
|
||||
.child(new NoteActionsWidget().hideInZenMode())
|
||||
)
|
||||
@@ -145,6 +144,7 @@ export default class DesktopLayout {
|
||||
.hideInZenMode()
|
||||
.child(new NoteInfoWidget())
|
||||
.child(new TabCachingWidget(() => new CalendarWidget()))
|
||||
.child(new TabCachingWidget(() => new EditedNotesWidget()))
|
||||
.child(new TabCachingWidget(() => new AttributesWidget()))
|
||||
.child(new TabCachingWidget(() => new LinkMapWidget()))
|
||||
.child(new TabCachingWidget(() => new NoteRevisionsWidget()))
|
||||
@@ -155,4 +155,4 @@ export default class DesktopLayout {
|
||||
.child(new SidePaneToggles().hideInZenMode())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import FlexContainer from "./flex_container.js";
|
||||
import NoteTitleWidget from "./note_title.js";
|
||||
import NoteDetailWidget from "./note_detail.js";
|
||||
import NoteTreeWidget from "./note_tree.js";
|
||||
import MobileGlobalButtonsWidget from "./mobile_global_buttons.js";
|
||||
import CloseDetailButtonWidget from "./close_detail_button.js";
|
||||
import MobileDetailMenuWidget from "./mobile_detail_menu.js";
|
||||
import ScreenContainer from "./screen_container.js";
|
||||
import FlexContainer from "../widgets/flex_container.js";
|
||||
import NoteTitleWidget from "../widgets/note_title.js";
|
||||
import NoteDetailWidget from "../widgets/note_detail.js";
|
||||
import NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import MobileGlobalButtonsWidget from "../widgets/mobile_widgets/mobile_global_buttons.js";
|
||||
import CloseDetailButtonWidget from "../widgets/mobile_widgets/close_detail_button.js";
|
||||
import MobileDetailMenuWidget from "../widgets/mobile_widgets/mobile_detail_menu.js";
|
||||
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
|
||||
|
||||
const MOBILE_CSS = `
|
||||
<style>
|
||||
@@ -73,7 +73,7 @@ export default class MobileLayout {
|
||||
.child(new ScreenContainer("tree", 'column')
|
||||
.class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-5 col-md-4 col-lg-4 col-xl-4")
|
||||
.child(new MobileGlobalButtonsWidget())
|
||||
.child(new NoteTreeWidget().cssBlock(FANCYTREE_CSS)))
|
||||
.child(new NoteTreeWidget("main").cssBlock(FANCYTREE_CSS)))
|
||||
.child(new ScreenContainer("detail", "column")
|
||||
.class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-7 col-md-8 col-lg-8")
|
||||
.child(new FlexContainer('row')
|
||||
@@ -1,5 +1,5 @@
|
||||
import appContext from "./services/app_context.js";
|
||||
import MobileLayout from "./widgets/mobile_layout.js";
|
||||
import MobileLayout from "./layouts/mobile_layout.js";
|
||||
import glob from "./services/glob.js";
|
||||
|
||||
glob.setupGlobs();
|
||||
|
||||
@@ -4,15 +4,22 @@ import DialogCommandExecutor from "./dialog_command_executor.js";
|
||||
import Entrypoints from "./entrypoints.js";
|
||||
import options from "./options.js";
|
||||
import utils from "./utils.js";
|
||||
import ZoomService from "./zoom.js";
|
||||
import zoomService from "./zoom.js";
|
||||
import TabManager from "./tab_manager.js";
|
||||
import treeService from "./tree.js";
|
||||
import Component from "../widgets/component.js";
|
||||
import keyboardActionsService from "./keyboard_actions.js";
|
||||
import MobileScreenSwitcherExecutor from "../widgets/mobile_screen_switcher.js";
|
||||
import MobileScreenSwitcherExecutor from "../widgets/mobile_widgets/mobile_screen_switcher.js";
|
||||
import MainTreeExecutors from "./main_tree_executors.js";
|
||||
|
||||
class AppContext extends Component {
|
||||
constructor(isMainWindow) {
|
||||
super();
|
||||
|
||||
this.isMainWindow = isMainWindow;
|
||||
this.executors = [];
|
||||
}
|
||||
|
||||
setLayout(layout) {
|
||||
this.layout = layout;
|
||||
}
|
||||
@@ -39,10 +46,12 @@ class AppContext extends Component {
|
||||
|
||||
$("body").append($renderedWidget);
|
||||
|
||||
$renderedWidget.on('click', "[data-trigger-command]", e => {
|
||||
const commandName = $(e.target).attr('data-trigger-command');
|
||||
$renderedWidget.on('click', "[data-trigger-command]", function() {
|
||||
const commandName = $(this).attr('data-trigger-command');
|
||||
const $component = $(this).closest(".component");
|
||||
const component = $component.prop("component");
|
||||
|
||||
this.triggerCommand(commandName);
|
||||
component.triggerCommand(commandName, {$el: $(this)});
|
||||
});
|
||||
|
||||
this.tabManager = new TabManager();
|
||||
@@ -65,7 +74,7 @@ class AppContext extends Component {
|
||||
}
|
||||
|
||||
if (utils.isElectron()) {
|
||||
this.child(new ZoomService());
|
||||
this.child(zoomService);
|
||||
}
|
||||
|
||||
this.triggerEvent('initialRenderComplete');
|
||||
@@ -86,6 +95,8 @@ class AppContext extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// this might hint at error but sometimes this is used by components which are at different places
|
||||
// in the component tree to communicate with each other
|
||||
console.debug(`Unhandled command ${name}, converting to event.`);
|
||||
|
||||
return this.triggerEvent(name, data);
|
||||
@@ -94,15 +105,9 @@ class AppContext extends Component {
|
||||
getComponentByEl(el) {
|
||||
return $(el).closest(".component").prop('component');
|
||||
}
|
||||
|
||||
async protectedSessionStartedEvent() {
|
||||
await treeCache.loadInitialTree();
|
||||
|
||||
this.triggerEvent('treeCacheReloaded');
|
||||
}
|
||||
}
|
||||
|
||||
const appContext = new AppContext();
|
||||
const appContext = new AppContext(window.glob.isMainWindow);
|
||||
|
||||
// we should save all outstanding changes before the page/app is closed
|
||||
$(window).on('beforeunload', () => {
|
||||
@@ -130,4 +135,4 @@ $(window).on('hashchange', function() {
|
||||
}
|
||||
});
|
||||
|
||||
export default appContext;
|
||||
export default appContext;
|
||||
|
||||
@@ -45,7 +45,7 @@ async function moveAfterBranch(branchIdsToMove, afterBranchId) {
|
||||
}
|
||||
}
|
||||
|
||||
async function moveToParentNote(branchIdsToMove, newParentNoteId) {
|
||||
async function moveToParentNote(branchIdsToMove, newParentBranchId) {
|
||||
branchIdsToMove = filterRootNote(branchIdsToMove);
|
||||
|
||||
for (const branchIdToMove of branchIdsToMove) {
|
||||
@@ -56,7 +56,7 @@ async function moveToParentNote(branchIdsToMove, newParentNoteId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const resp = await server.put(`branches/${branchIdToMove}/move-to/${newParentNoteId}`);
|
||||
const resp = await server.put(`branches/${branchIdToMove}/move-to/${newParentBranchId}`);
|
||||
|
||||
if (!resp.success) {
|
||||
alert(resp.message);
|
||||
@@ -198,8 +198,8 @@ ws.subscribeToMessages(async message => {
|
||||
}
|
||||
});
|
||||
|
||||
async function cloneNoteTo(childNoteId, parentNoteId, prefix) {
|
||||
const resp = await server.put('notes/' + childNoteId + '/clone-to/' + parentNoteId, {
|
||||
async function cloneNoteTo(childNoteId, parentBranchId, prefix) {
|
||||
const resp = await server.put(`notes/${childNoteId}/clone-to/${parentBranchId}`, {
|
||||
prefix: prefix
|
||||
});
|
||||
|
||||
@@ -225,4 +225,4 @@ export default {
|
||||
moveNodeUpInHierarchy,
|
||||
cloneNoteAfter,
|
||||
cloneNoteTo
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import ScriptContext from "./script_context.js";
|
||||
import server from "./server.js";
|
||||
import toastService from "./toast.js";
|
||||
import treeCache from "./tree_cache.js";
|
||||
|
||||
async function getAndExecuteBundle(noteId, originEntity = null) {
|
||||
const bundle = await server.get('script/bundle/' + noteId);
|
||||
@@ -77,4 +76,4 @@ export default {
|
||||
getAndExecuteBundle,
|
||||
executeStartupBundles,
|
||||
getWidgetBundlesByParent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,13 +33,13 @@ async function pasteAfter(afterBranchId) {
|
||||
}
|
||||
}
|
||||
|
||||
async function pasteInto(parentNoteId) {
|
||||
async function pasteInto(parentBranchId) {
|
||||
if (isClipboardEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (clipboardMode === 'cut') {
|
||||
await branchService.moveToParentNote(clipboardBranchIds, parentNoteId);
|
||||
await branchService.moveToParentNote(clipboardBranchIds, parentBranchId);
|
||||
|
||||
clipboardBranchIds = [];
|
||||
clipboardMode = null;
|
||||
@@ -50,7 +50,7 @@ async function pasteInto(parentNoteId) {
|
||||
for (const clipboardBranch of clipboardBranches) {
|
||||
const clipboardNote = await clipboardBranch.getNote();
|
||||
|
||||
await branchService.cloneNoteTo(clipboardNote.noteId, parentNoteId);
|
||||
await branchService.cloneNoteTo(clipboardNote.noteId, parentBranchId);
|
||||
}
|
||||
|
||||
// copy will keep clipboardBranchIds and clipboardMode so it's possible to paste into multiple places
|
||||
@@ -89,4 +89,4 @@ export default {
|
||||
cut,
|
||||
copy,
|
||||
isClipboardEmpty
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import Component from "../widgets/component.js";
|
||||
import toastService from "./toast.js";
|
||||
import noteCreateService from "./note_create.js";
|
||||
import ws from "./ws.js";
|
||||
import bundleService from "./bundle.js";
|
||||
|
||||
export default class Entrypoints extends Component {
|
||||
constructor() {
|
||||
@@ -182,4 +183,40 @@ export default class Entrypoints extends Component {
|
||||
}
|
||||
|
||||
createTopLevelNoteCommand() { noteCreateService.createNewTopLevelNote(); }
|
||||
|
||||
async openInWindowCommand({notePath}) {
|
||||
if (utils.isElectron()) {
|
||||
const {ipcRenderer} = utils.dynamicRequire('electron');
|
||||
|
||||
ipcRenderer.send('create-extra-window', {notePath});
|
||||
}
|
||||
else {
|
||||
const url = window.location.protocol + '//' + window.location.host + window.location.pathname + '?extra=1#' + notePath;
|
||||
|
||||
window.open(url, '', 'width=1000,height=800');
|
||||
}
|
||||
}
|
||||
|
||||
async openNewWindowCommand() {
|
||||
this.openInWindowCommand({notePath: ''});
|
||||
}
|
||||
|
||||
async runActiveNoteCommand() {
|
||||
const note = appContext.tabManager.getActiveTabNote();
|
||||
|
||||
// ctrl+enter is also used elsewhere so make sure we're running only when appropriate
|
||||
if (!note || note.type !== 'code') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (note.mime.endsWith("env=frontend")) {
|
||||
await bundleService.getAndExecuteBundle(note.noteId);
|
||||
}
|
||||
|
||||
if (note.mime.endsWith("env=backend")) {
|
||||
await server.post('script/run/' + note.noteId);
|
||||
}
|
||||
|
||||
toastService.showMessage("Note executed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,6 +403,13 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
|
||||
* @method
|
||||
*/
|
||||
this.waitUntilSynced = ws.waitForMaxKnownSyncId;
|
||||
|
||||
/**
|
||||
* This will refresh all currently opened notes which have included note specified in the parameter
|
||||
*
|
||||
* @param includedNoteId - noteId of the included note
|
||||
*/
|
||||
this.refreshIncludedNote = includedNoteId => appContext.triggerEvent('refreshIncludedNote', {noteId: includedNoteId});
|
||||
}
|
||||
|
||||
export default FrontendScriptApi;
|
||||
export default FrontendScriptApi;
|
||||
|
||||
@@ -49,11 +49,7 @@ function setupGlobs() {
|
||||
|
||||
let message = "Uncaught error: ";
|
||||
|
||||
if (string.includes("Cannot read property 'defaultView' of undefined")) {
|
||||
// ignore this specific error which is very common but we don't know where it comes from
|
||||
// and it seems to be harmless
|
||||
return true;
|
||||
} else if (string.includes("script error")) {
|
||||
if (string.includes("script error")) {
|
||||
message += 'No details available';
|
||||
} else {
|
||||
message += [
|
||||
@@ -61,8 +57,9 @@ function setupGlobs() {
|
||||
'URL: ' + url,
|
||||
'Line: ' + lineNo,
|
||||
'Column: ' + columnNo,
|
||||
'Error object: ' + JSON.stringify(error)
|
||||
].join(' - ');
|
||||
'Error object: ' + JSON.stringify(error),
|
||||
'Stack: ' + error && error.stack
|
||||
].join(', ');
|
||||
}
|
||||
|
||||
ws.logError(message);
|
||||
@@ -91,4 +88,4 @@ function setupGlobs() {
|
||||
|
||||
export default {
|
||||
setupGlobs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,8 +93,8 @@ function updateDisplayedShortcuts($container) {
|
||||
}
|
||||
});
|
||||
|
||||
$container.find('button[data-command],a.icon-action[data-command],.kb-in-title').each(async (i, el) => {
|
||||
const actionName = $(el).attr('data-command');
|
||||
$container.find('[data-trigger-command]').each(async (i, el) => {
|
||||
const actionName = $(el).attr('data-trigger-command');
|
||||
const action = await getAction(actionName, true);
|
||||
|
||||
if (action) {
|
||||
|
||||
@@ -81,24 +81,29 @@ function goToLink(e) {
|
||||
}
|
||||
else if (e.which === 1) {
|
||||
const activeTabContext = appContext.tabManager.getActiveTabContext();
|
||||
activeTabContext.setNote(notePath)
|
||||
activeTabContext.setNote(notePath);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const address = $link.attr('href');
|
||||
if (e.which === 1) {
|
||||
const address = $link.attr('href');
|
||||
|
||||
if (address && address.startsWith('http')) {
|
||||
window.open(address, '_blank');
|
||||
if (address && address.startsWith('http')) {
|
||||
window.open(address, '_blank');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function newTabContextMenu(e) {
|
||||
function linkContextMenu(e) {
|
||||
const $link = $(e.target).closest("a");
|
||||
|
||||
const notePath = getNotePathFromLink($link);
|
||||
@@ -113,12 +118,16 @@ function newTabContextMenu(e) {
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
items: [
|
||||
{title: "Open note in new tab", command: "openNoteInNewTab", uiIcon: "arrow-up-right"}
|
||||
{title: "Open note in new tab", command: "openNoteInNewTab", uiIcon: "empty"},
|
||||
{title: "Open note in new window", command: "openNoteInNewWindow", uiIcon: "window-open"}
|
||||
],
|
||||
selectMenuItemHandler: ({command}) => {
|
||||
if (command === 'openNoteInNewTab') {
|
||||
appContext.tabManager.openTabWithNote(notePath);
|
||||
}
|
||||
else if (command === 'openNoteInNewWindow') {
|
||||
appContext.openInNewWindow(notePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -128,7 +137,7 @@ function newTabContextMenu(e) {
|
||||
$(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) {
|
||||
$(document).on('mousedown', '.note-detail-text a:not(.reference-link)', function (e) {
|
||||
const $link = $(e.target).closest("a");
|
||||
const notePath = getNotePathFromLink($link);
|
||||
|
||||
@@ -151,20 +160,26 @@ $(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,.note-detail-text a.reference-link', goToLink);
|
||||
$(document).on('mousedown', '.note-detail-text a.reference-link', goToLink);
|
||||
$(document).on('mousedown', '.note-detail-readonly-text a.reference-link', goToLink);
|
||||
$(document).on('mousedown', '.note-detail-readonly-text a', goToLink);
|
||||
$(document).on('mousedown', 'a.ck-link-actions__preview', goToLink);
|
||||
$(document).on('click', 'section.include-note a', 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);
|
||||
$(document).on('contextmenu', 'a.ck-link-actions__preview', linkContextMenu);
|
||||
$(document).on('contextmenu', '.note-detail-text a', linkContextMenu);
|
||||
$(document).on('contextmenu', '.note-detail-readonly-text a', linkContextMenu);
|
||||
$(document).on('contextmenu', "a[data-action='note']", linkContextMenu);
|
||||
$(document).on('contextmenu', ".note-detail-render a", linkContextMenu);
|
||||
$(document).on('contextmenu', ".note-paths-widget a", linkContextMenu);
|
||||
$(document).on('contextmenu', "section.include-note a", linkContextMenu);
|
||||
|
||||
export default {
|
||||
getNotePathFromUrl,
|
||||
createNoteLink,
|
||||
goToLink
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ export default class LoadResults {
|
||||
|
||||
this.noteIdToSourceId = {};
|
||||
this.sourceIdToNoteIds = {};
|
||||
|
||||
|
||||
this.branches = [];
|
||||
|
||||
this.attributes = [];
|
||||
@@ -54,8 +54,9 @@ export default class LoadResults {
|
||||
this.attributes.push({attributeId, sourceId});
|
||||
}
|
||||
|
||||
getAttributes() {
|
||||
getAttributes(sourceId = 'none') {
|
||||
return this.attributes
|
||||
.filter(row => row.sourceId !== sourceId)
|
||||
.map(row => this.treeCache.attributes[row.attributeId])
|
||||
.filter(attr => !!attr);
|
||||
}
|
||||
@@ -101,6 +102,15 @@ export default class LoadResults {
|
||||
this.options.includes(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} true if there are changes which could affect the attributes (including inherited ones)
|
||||
* notably changes in note itself should not have any effect on attributes
|
||||
*/
|
||||
hasAttributeRelatedChanges() {
|
||||
return this.branches.length === 0
|
||||
&& this.attributes.length === 0;
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return Object.keys(this.noteIdToSourceId).length === 0
|
||||
&& this.branches.length === 0
|
||||
@@ -110,4 +120,4 @@ export default class LoadResults {
|
||||
&& this.contentNoteIdToSourceId.length === 0
|
||||
&& this.options.length === 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,14 +34,10 @@ export default class MainTreeExecutors extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const {note} = await noteCreateService.createNote(activeNote.noteId, {
|
||||
await noteCreateService.createNote(activeNote.noteId, {
|
||||
isProtected: activeNote.isProtected,
|
||||
saveSelection: false
|
||||
});
|
||||
|
||||
await ws.waitForMaxKnownSyncId();
|
||||
|
||||
appContext.tabManager.getActiveTabContext().setNote(note.noteId);
|
||||
}
|
||||
|
||||
async createNoteAfterCommand() {
|
||||
@@ -53,15 +49,11 @@ export default class MainTreeExecutors extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const {note} = await noteCreateService.createNote(parentNoteId, {
|
||||
await noteCreateService.createNote(parentNoteId, {
|
||||
target: 'after',
|
||||
targetBranchId: node.data.branchId,
|
||||
isProtected: isProtected,
|
||||
saveSelection: true
|
||||
saveSelection: false
|
||||
});
|
||||
|
||||
await ws.waitForMaxKnownSyncId();
|
||||
|
||||
appContext.tabManager.getActiveTabContext().setNote(note.noteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
export default class Mutex {
|
||||
constructor() {
|
||||
this.queue = [];
|
||||
this.pending = false;
|
||||
}
|
||||
|
||||
isLocked() {
|
||||
return this.pending;
|
||||
}
|
||||
|
||||
acquire() {
|
||||
const ticket = new Promise(resolve => this.queue.push(resolve));
|
||||
|
||||
if (!this.pending) {
|
||||
this.dispatchNext();
|
||||
}
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
dispatchNext() {
|
||||
if (this.queue.length > 0) {
|
||||
this.pending = true;
|
||||
this.queue.shift()(this.dispatchNext.bind(this));
|
||||
} else {
|
||||
this.pending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/public/app/services/note_attribute_cache.js
Normal file
20
src/public/app/services/note_attribute_cache.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Purpose of this class is to cache list of attributes for notes.
|
||||
*
|
||||
* Cache invalidation granularity is global - whenever a write operation is detected to notes, branches or attributes
|
||||
* we invalidate the whole cache. That's OK, since the purpose for this is to speed up batch read-only operations, such
|
||||
* as loading the tree which uses attributes heavily.
|
||||
*/
|
||||
class NoteAttributeCache {
|
||||
constructor() {
|
||||
this.attributes = {};
|
||||
}
|
||||
|
||||
invalidate() {
|
||||
this.attributes = {};
|
||||
}
|
||||
}
|
||||
|
||||
const noteAttributeCache = new NoteAttributeCache();
|
||||
|
||||
export default noteAttributeCache;
|
||||
@@ -48,8 +48,12 @@ async function createNote(parentNoteId, options = {}) {
|
||||
}
|
||||
|
||||
if (options.activate) {
|
||||
await ws.waitForMaxKnownSyncId();
|
||||
|
||||
const activeTabContext = appContext.tabManager.getActiveTabContext();
|
||||
activeTabContext.setNote(note.noteId);
|
||||
await activeTabContext.setNote(note.noteId);
|
||||
|
||||
appContext.triggerCommand('focusAndSelectTitle');
|
||||
}
|
||||
|
||||
return {note, branch};
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import treeService from './tree.js';
|
||||
import utils from './utils.js';
|
||||
import server from './server.js';
|
||||
import protectedSessionHolder from './protected_session_holder.js';
|
||||
import toastService from "./toast.js";
|
||||
import ws from "./ws.js";
|
||||
import appContext from "./app_context.js";
|
||||
|
||||
const $enterProtectedSessionButton = $("#enter-protected-session-button");
|
||||
const $leaveProtectedSessionButton = $("#leave-protected-session-button");
|
||||
import treeCache from "./tree_cache.js";
|
||||
|
||||
let protectedSessionDeferred = null;
|
||||
|
||||
async function leaveProtectedSession() {
|
||||
if (protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||
utils.reloadApp();
|
||||
protectedSessionHolder.resetProtectedSession();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +31,15 @@ function enterProtectedSession() {
|
||||
return dfd.promise();
|
||||
}
|
||||
|
||||
async function reloadData() {
|
||||
const allNoteIds = Object.keys(treeCache.notes);
|
||||
|
||||
await treeCache.loadInitialTree();
|
||||
|
||||
// make sure that all notes used in the application are loaded, including the ones not shown in the tree
|
||||
await treeCache.reloadNotes(allNoteIds, true);
|
||||
}
|
||||
|
||||
async function setupProtectedSession(password) {
|
||||
const response = await enterProtectedSessionOnServer(password);
|
||||
|
||||
@@ -45,6 +51,10 @@ async function setupProtectedSession(password) {
|
||||
protectedSessionHolder.setProtectedSessionId(response.protectedSessionId);
|
||||
protectedSessionHolder.touchProtectedSession();
|
||||
|
||||
await reloadData();
|
||||
|
||||
await appContext.triggerEvent('treeCacheReloaded');
|
||||
|
||||
appContext.triggerEvent('protectedSessionStarted');
|
||||
|
||||
if (protectedSessionDeferred !== null) {
|
||||
@@ -54,9 +64,6 @@ async function setupProtectedSession(password) {
|
||||
protectedSessionDeferred = null;
|
||||
}
|
||||
|
||||
$enterProtectedSessionButton.hide();
|
||||
$leaveProtectedSessionButton.show();
|
||||
|
||||
toastService.showMessage("Protected session has been started.");
|
||||
}
|
||||
|
||||
@@ -106,4 +113,4 @@ export default {
|
||||
enterProtectedSession,
|
||||
leaveProtectedSession,
|
||||
setupProtectedSession
|
||||
};
|
||||
};
|
||||
|
||||
@@ -39,9 +39,16 @@ function touchProtectedSession() {
|
||||
}
|
||||
}
|
||||
|
||||
function touchProtectedSessionIfNecessary(note) {
|
||||
if (note && note.isProtected && isProtectedSessionAvailable()) {
|
||||
touchProtectedSession();
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
setProtectedSessionId,
|
||||
resetProtectedSession,
|
||||
isProtectedSessionAvailable,
|
||||
touchProtectedSession
|
||||
touchProtectedSession,
|
||||
touchProtectedSessionIfNecessary
|
||||
};
|
||||
@@ -8,6 +8,7 @@ function getHeaders(headers) {
|
||||
// also avoiding using underscores instead of dashes since nginx filters them out by default
|
||||
const allHeaders = {
|
||||
'trilium-source-id': glob.sourceId,
|
||||
'trilium-local-now-datetime': utils.localNowDateTime(),
|
||||
'x-csrf-token': glob.csrfToken
|
||||
};
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ async function syncNow() {
|
||||
toastService.showMessage("Sync finished successfully.");
|
||||
}
|
||||
else {
|
||||
if (result.message.length > 50) {
|
||||
result.message = result.message.substr(0, 50);
|
||||
if (result.message.length > 100) {
|
||||
result.message = result.message.substr(0, 100);
|
||||
}
|
||||
|
||||
toastService.showError("Sync failed: " + result.message);
|
||||
@@ -25,4 +25,4 @@ async function forceNoteSync(noteId) {
|
||||
export default {
|
||||
syncNow,
|
||||
forceNoteSync
|
||||
};
|
||||
};
|
||||
|
||||
@@ -58,6 +58,7 @@ class TabContext extends Component {
|
||||
|
||||
this.autoBookDisabled = false;
|
||||
this.textPreviewDisabled = false;
|
||||
this.codePreviewDisabled = false;
|
||||
|
||||
setTimeout(async () => {
|
||||
// we include the note into recent list only if the user stayed on the note at least 5 seconds
|
||||
@@ -69,13 +70,10 @@ class TabContext extends Component {
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
if (this.note.isProtected && protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||
// FIXME: there are probably more places where this should be done
|
||||
protectedSessionHolder.touchProtectedSession();
|
||||
}
|
||||
protectedSessionHolder.touchProtectedSessionIfNecessary(this.note);
|
||||
|
||||
if (triggerSwitchEvent) {
|
||||
this.triggerEvent('tabNoteSwitched', {
|
||||
await this.triggerEvent('tabNoteSwitched', {
|
||||
tabContext: this,
|
||||
notePath: this.notePath
|
||||
});
|
||||
@@ -129,4 +127,4 @@ class TabContext extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default TabContext;
|
||||
export default TabContext;
|
||||
|
||||
@@ -6,6 +6,7 @@ import treeCache from "./tree_cache.js";
|
||||
import treeService from "./tree.js";
|
||||
import utils from "./utils.js";
|
||||
import TabContext from "./tab_context.js";
|
||||
import appContext from "./app_context.js";
|
||||
|
||||
export default class TabManager extends Component {
|
||||
constructor() {
|
||||
@@ -14,6 +15,10 @@ export default class TabManager extends Component {
|
||||
this.activeTabId = null;
|
||||
|
||||
this.tabsUpdate = new SpacedUpdate(async () => {
|
||||
if (!appContext.isMainWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const openTabs = this.tabContexts
|
||||
.map(tc => tc.getTabState())
|
||||
.filter(t => !!t);
|
||||
@@ -30,7 +35,9 @@ export default class TabManager extends Component {
|
||||
}
|
||||
|
||||
async loadTabs() {
|
||||
const openTabs = options.getJson('openTabs') || [];
|
||||
const tabsToOpen = appContext.isMainWindow
|
||||
? (options.getJson('openTabs') || [])
|
||||
: [];
|
||||
|
||||
// if there's notePath in the URL, make sure it's open and active
|
||||
// (useful, among others, for opening clipped notes from clipper)
|
||||
@@ -39,17 +46,17 @@ export default class TabManager extends Component {
|
||||
const noteId = treeService.getNoteIdFromNotePath(notePath);
|
||||
|
||||
if (noteId && await treeCache.noteExists(noteId)) {
|
||||
for (const tab of openTabs) {
|
||||
for (const tab of tabsToOpen) {
|
||||
tab.active = false;
|
||||
}
|
||||
|
||||
const foundTab = openTabs.find(tab => noteId === treeService.getNoteIdFromNotePath(tab.notePath));
|
||||
const foundTab = tabsToOpen.find(tab => noteId === treeService.getNoteIdFromNotePath(tab.notePath));
|
||||
|
||||
if (foundTab) {
|
||||
foundTab.active = true;
|
||||
}
|
||||
else {
|
||||
openTabs.push({
|
||||
tabsToOpen.push({
|
||||
notePath: notePath,
|
||||
active: true
|
||||
});
|
||||
@@ -59,7 +66,7 @@ export default class TabManager extends Component {
|
||||
|
||||
let filteredTabs = [];
|
||||
|
||||
for (const openTab of openTabs) {
|
||||
for (const openTab of tabsToOpen) {
|
||||
const noteId = treeService.getNoteIdFromNotePath(openTab.notePath);
|
||||
|
||||
if (await treeCache.noteExists(noteId)) {
|
||||
@@ -75,7 +82,7 @@ export default class TabManager extends Component {
|
||||
|
||||
if (filteredTabs.length === 0) {
|
||||
filteredTabs.push({
|
||||
notePath: 'root',
|
||||
notePath: this.isMainWindow ? 'root' : '',
|
||||
active: true
|
||||
});
|
||||
}
|
||||
@@ -189,12 +196,14 @@ export default class TabManager extends Component {
|
||||
async openTabWithNote(notePath, activate, tabId = null) {
|
||||
const tabContext = await this.openEmptyTab(tabId);
|
||||
|
||||
await tabContext.setNote(notePath, !activate); // if activate is false then send normal noteSwitched event
|
||||
if (notePath) {
|
||||
await tabContext.setNote(notePath, !activate); // if activate is false then send normal noteSwitched event
|
||||
}
|
||||
|
||||
if (activate) {
|
||||
this.activateTab(tabContext.tabId, false);
|
||||
|
||||
this.triggerEvent('tabNoteSwitchedAndActivated', {
|
||||
await this.triggerEvent('tabNoteSwitchedAndActivated', {
|
||||
tabContext,
|
||||
notePath: tabContext.notePath // resolved note path
|
||||
});
|
||||
@@ -229,7 +238,7 @@ export default class TabManager extends Component {
|
||||
}
|
||||
|
||||
this.tabsUpdate.scheduleUpdate();
|
||||
|
||||
|
||||
this.setCurrentNotePathToHash();
|
||||
}
|
||||
|
||||
@@ -240,6 +249,9 @@ export default class TabManager extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
// close dangling autocompletes after closing the tab
|
||||
$(".aa-input").autocomplete("close");
|
||||
|
||||
await this.triggerEvent('beforeTabRemove', {tabId});
|
||||
|
||||
if (this.tabContexts.length <= 1) {
|
||||
@@ -315,6 +327,14 @@ export default class TabManager extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
moveTabToNewWindowCommand({tabId}) {
|
||||
const notePath = this.getTabContextById(tabId).notePath;
|
||||
|
||||
this.removeTab(tabId);
|
||||
|
||||
this.triggerCommand('openInWindow', {notePath});
|
||||
}
|
||||
|
||||
async hoistedNoteChangedEvent({hoistedNoteId}) {
|
||||
if (hoistedNoteId === 'root') {
|
||||
return;
|
||||
@@ -326,4 +346,4 @@ export default class TabManager extends Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ async function resolveNotePath(notePath) {
|
||||
*
|
||||
* @return {string[]}
|
||||
*/
|
||||
async function getRunPath(notePath) {
|
||||
async function getRunPath(notePath, logErrors = true) {
|
||||
utils.assertArguments(notePath);
|
||||
|
||||
notePath = notePath.split("-")[0].trim();
|
||||
@@ -66,10 +66,14 @@ async function getRunPath(notePath) {
|
||||
}
|
||||
|
||||
if (!parents.some(p => p.noteId === parentNoteId)) {
|
||||
console.debug(utils.now(), "Did not find parent " + parentNoteId + " for child " + childNoteId);
|
||||
if (logErrors) {
|
||||
console.log(utils.now(), "Did not find parent " + parentNoteId + " for child " + childNoteId);
|
||||
}
|
||||
|
||||
if (parents.length > 0) {
|
||||
console.debug(utils.now(), "Available parents:", parents);
|
||||
if (logErrors) {
|
||||
console.log(utils.now(), "Available parents:", parents);
|
||||
}
|
||||
|
||||
const someNotePath = await getSomeNotePath(parents[0]);
|
||||
|
||||
@@ -86,7 +90,10 @@ async function getRunPath(notePath) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
console.log("No parents so no run path.");
|
||||
if (logErrors) {
|
||||
console.log("No parents so no run path.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -162,6 +169,13 @@ function getNoteIdFromNotePath(notePath) {
|
||||
}
|
||||
|
||||
function getNoteIdAndParentIdFromNotePath(notePath) {
|
||||
if (notePath === 'root') {
|
||||
return {
|
||||
noteId: 'root',
|
||||
parentNoteId: 'none'
|
||||
};
|
||||
}
|
||||
|
||||
let parentNoteId = 'root';
|
||||
let noteId = '';
|
||||
|
||||
@@ -286,4 +300,4 @@ export default {
|
||||
getNotePathTitle,
|
||||
getHashValueFromAddress,
|
||||
parseNotePath
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
import utils from "./utils.js";
|
||||
import treeCache from "./tree_cache.js";
|
||||
import ws from "./ws.js";
|
||||
import hoistedNoteService from "./hoisted_note.js";
|
||||
|
||||
async function prepareRootNode() {
|
||||
await treeCache.initializedPromise;
|
||||
|
||||
const hoistedNoteId = hoistedNoteService.getHoistedNoteId();
|
||||
|
||||
let hoistedBranch;
|
||||
|
||||
if (hoistedNoteId === 'root') {
|
||||
hoistedBranch = treeCache.getBranch('root');
|
||||
}
|
||||
else {
|
||||
const hoistedNote = await treeCache.getNote(hoistedNoteId);
|
||||
hoistedBranch = (await hoistedNote.getBranches())[0];
|
||||
}
|
||||
|
||||
return await prepareNode(hoistedBranch);
|
||||
}
|
||||
|
||||
async function prepareBranch(note) {
|
||||
if (note.type === 'search') {
|
||||
return await prepareSearchBranch(note);
|
||||
}
|
||||
else {
|
||||
return await prepareRealBranch(note);
|
||||
}
|
||||
}
|
||||
|
||||
const NOTE_TYPE_ICONS = {
|
||||
"file": "bx bx-file",
|
||||
"image": "bx bx-image",
|
||||
"code": "bx bx-code",
|
||||
"render": "bx bx-extension",
|
||||
"search": "bx bx-file-find",
|
||||
"relation-map": "bx bx-map-alt",
|
||||
"book": "bx bx-book"
|
||||
};
|
||||
|
||||
function getIconClass(note) {
|
||||
const labels = note.getLabels('iconClass');
|
||||
|
||||
return labels.map(l => l.value).join(' ');
|
||||
}
|
||||
|
||||
function getIcon(note) {
|
||||
const hoistedNoteId = hoistedNoteService.getHoistedNoteId();
|
||||
|
||||
const iconClass = getIconClass(note);
|
||||
|
||||
if (iconClass) {
|
||||
return iconClass;
|
||||
}
|
||||
else if (note.noteId === 'root') {
|
||||
return "bx bx-chevrons-right";
|
||||
}
|
||||
else if (note.noteId === hoistedNoteId) {
|
||||
return "bx bxs-arrow-from-bottom";
|
||||
}
|
||||
else if (note.type === 'text') {
|
||||
if (note.hasChildren()) {
|
||||
return "bx bx-folder";
|
||||
}
|
||||
else {
|
||||
return "bx bx-note";
|
||||
}
|
||||
}
|
||||
else {
|
||||
return NOTE_TYPE_ICONS[note.type];
|
||||
}
|
||||
}
|
||||
|
||||
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 = hoistedNoteService.getHoistedNoteId();
|
||||
|
||||
const node = {
|
||||
noteId: note.noteId,
|
||||
parentNoteId: branch.parentNoteId,
|
||||
branchId: branch.branchId,
|
||||
isProtected: note.isProtected,
|
||||
noteType: note.type,
|
||||
title: utils.escapeHtml(title),
|
||||
extraClasses: getExtraClasses(note),
|
||||
icon: getIcon(note),
|
||||
refKey: note.noteId,
|
||||
expanded: branch.isExpanded || hoistedNoteId === note.noteId,
|
||||
lazy: true,
|
||||
key: utils.randomString(12) // this should prevent some "duplicate key" errors
|
||||
};
|
||||
|
||||
if (note.hasChildren() || note.type === 'search') {
|
||||
node.folder = true;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
async function prepareRealBranch(parentNote) {
|
||||
utils.assertArguments(parentNote);
|
||||
|
||||
const childBranches = await parentNote.getChildBranches();
|
||||
|
||||
if (!childBranches) {
|
||||
ws.logError(`No children for ${parentNote}. This shouldn't happen.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const noteList = [];
|
||||
|
||||
for (const branch of childBranches) {
|
||||
const node = await prepareNode(branch);
|
||||
|
||||
noteList.push(node);
|
||||
}
|
||||
|
||||
return noteList;
|
||||
}
|
||||
|
||||
async function prepareSearchBranch(note) {
|
||||
await treeCache.reloadNotes([note.noteId]);
|
||||
|
||||
const newNote = await treeCache.getNote(note.noteId);
|
||||
|
||||
return await prepareRealBranch(newNote);
|
||||
}
|
||||
|
||||
function getExtraClasses(note) {
|
||||
utils.assertArguments(note);
|
||||
|
||||
const extraClasses = [];
|
||||
|
||||
if (note.isProtected) {
|
||||
extraClasses.push("protected");
|
||||
}
|
||||
|
||||
if (note.getParentNoteIds().length > 1) {
|
||||
extraClasses.push("multiple-parents");
|
||||
}
|
||||
|
||||
const cssClass = note.getCssClass();
|
||||
|
||||
if (cssClass) {
|
||||
extraClasses.push(cssClass);
|
||||
}
|
||||
|
||||
extraClasses.push(utils.getNoteTypeClass(note.type));
|
||||
|
||||
if (note.mime) { // some notes should not have mime type (e.g. render)
|
||||
extraClasses.push(utils.getMimeTypeClass(note.mime));
|
||||
}
|
||||
|
||||
if (note.hasLabel('archived')) {
|
||||
extraClasses.push("archived");
|
||||
}
|
||||
|
||||
return extraClasses.join(" ");
|
||||
}
|
||||
|
||||
export default {
|
||||
prepareRootNode,
|
||||
prepareBranch,
|
||||
getExtraClasses,
|
||||
getIcon
|
||||
}
|
||||
@@ -20,6 +20,8 @@ class TreeCache {
|
||||
async loadInitialTree() {
|
||||
const resp = await server.get('tree');
|
||||
|
||||
await this.loadParents(resp, false);
|
||||
|
||||
// clear the cache only directly before adding new content which is important for e.g. switching to protected session
|
||||
|
||||
/** @type {Object.<string, NoteShort>} */
|
||||
@@ -34,24 +36,28 @@ class TreeCache {
|
||||
/** @type {Object.<string, Promise<NoteComplement>>} */
|
||||
this.noteComplementPromises = {};
|
||||
|
||||
await this.loadParents(resp);
|
||||
this.addResp(resp);
|
||||
}
|
||||
|
||||
async loadParents(resp) {
|
||||
async loadParents(resp, additiveLoad) {
|
||||
const noteIds = new Set(resp.notes.map(note => note.noteId));
|
||||
const missingNoteIds = [];
|
||||
const existingNotes = additiveLoad ? this.notes : {};
|
||||
|
||||
for (const branch of resp.branches) {
|
||||
if (!(branch.parentNoteId in this.notes) && !noteIds.has(branch.parentNoteId) && branch.parentNoteId !== 'none') {
|
||||
if (!(branch.parentNoteId in existingNotes) && !noteIds.has(branch.parentNoteId) && branch.parentNoteId !== 'none') {
|
||||
missingNoteIds.push(branch.parentNoteId);
|
||||
}
|
||||
}
|
||||
|
||||
for (const attr of resp.attributes) {
|
||||
if (attr.type === 'relation' && attr.name === 'template' && !(attr.value in this.notes) && !noteIds.has(attr.value)) {
|
||||
if (attr.type === 'relation' && attr.name === 'template' && !(attr.value in existingNotes) && !noteIds.has(attr.value)) {
|
||||
missingNoteIds.push(attr.value);
|
||||
}
|
||||
|
||||
if (!(attr.noteId in existingNotes) && !noteIds.has(attr.noteId)) {
|
||||
missingNoteIds.push(attr.noteId);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingNoteIds.length > 0) {
|
||||
@@ -61,7 +67,7 @@ class TreeCache {
|
||||
resp.branches = resp.branches.concat(newResp.branches);
|
||||
resp.attributes = resp.attributes.concat(newResp.attributes);
|
||||
|
||||
await this.loadParents(resp);
|
||||
await this.loadParents(resp, additiveLoad);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +160,7 @@ class TreeCache {
|
||||
|
||||
const resp = await server.post('tree/load', { noteIds });
|
||||
|
||||
await this.loadParents(resp);
|
||||
await this.loadParents(resp, true);
|
||||
this.addResp(resp);
|
||||
|
||||
for (const note of resp.notes) {
|
||||
@@ -231,7 +237,7 @@ class TreeCache {
|
||||
/** @return {Promise<NoteShort>} */
|
||||
async getNote(noteId, silentNotFoundError = false) {
|
||||
if (noteId === 'none') {
|
||||
console.log(`No 'none' note.`);
|
||||
console.trace(`No 'none' note.`);
|
||||
return null;
|
||||
}
|
||||
else if (!noteId) {
|
||||
@@ -246,10 +252,10 @@ class TreeCache {
|
||||
return this.notes[noteId];
|
||||
}
|
||||
|
||||
getBranches(branchIds) {
|
||||
getBranches(branchIds, silentNotFoundError = false) {
|
||||
return branchIds
|
||||
.map(branchId => this.getBranch(branchId))
|
||||
.filter(b => b !== null);
|
||||
.map(branchId => this.getBranch(branchId, silentNotFoundError))
|
||||
.filter(b => !!b);
|
||||
}
|
||||
|
||||
/** @return {Branch} */
|
||||
@@ -270,6 +276,9 @@ class TreeCache {
|
||||
return child.parentToBranch[parentNoteId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise<NoteComplement>}
|
||||
*/
|
||||
async getNoteComplement(noteId) {
|
||||
if (!this.noteComplementPromises[noteId]) {
|
||||
this.noteComplementPromises[noteId] = server.get('notes/' + noteId).then(row => new NoteComplement(row));
|
||||
@@ -281,4 +290,4 @@ class TreeCache {
|
||||
|
||||
const treeCache = new TreeCache();
|
||||
|
||||
export default treeCache;
|
||||
export default treeCache;
|
||||
|
||||
@@ -40,9 +40,9 @@ class TreeContextMenu {
|
||||
async getMenuItems() {
|
||||
const note = await treeCache.getNote(this.node.data.noteId);
|
||||
const branch = treeCache.getBranch(this.node.data.branchId);
|
||||
const parentNote = await treeCache.getNote(branch.parentNoteId);
|
||||
const isNotRoot = note.noteId !== 'root';
|
||||
const isHoisted = note.noteId === hoistedNoteService.getHoistedNoteId();
|
||||
const parentNote = isNotRoot ? await treeCache.getNote(branch.parentNoteId) : null;
|
||||
|
||||
// some actions don't support multi-note so they are disabled when notes are selected
|
||||
// the only exception is when the only selected note is the one that was right-clicked, then
|
||||
@@ -56,7 +56,8 @@ class TreeContextMenu {
|
||||
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
|
||||
|
||||
return [
|
||||
{ title: 'Open in new tab', command: "openInTab", uiIcon: "empty", enabled: noSelectedNotes },
|
||||
{ title: 'Open in a new tab <kbd>Ctrl+Click</kbd>', command: "openInTab", uiIcon: "empty", enabled: noSelectedNotes },
|
||||
{ title: 'Open in a new window', command: "openInWindow", uiIcon: "window-open", enabled: noSelectedNotes },
|
||||
{ title: 'Insert note after <kbd data-command="createNoteAfter"></kbd>', command: "insertNoteAfter", uiIcon: "plus",
|
||||
items: insertNoteAfterEnabled ? this.getNoteTypeItems("insertNoteAfter") : null,
|
||||
enabled: insertNoteAfterEnabled && noSelectedNotes },
|
||||
@@ -73,7 +74,8 @@ class TreeContextMenu {
|
||||
{ title: 'Edit branch prefix <kbd data-command="editBranchPrefix"></kbd>', command: "editBranchPrefix", uiIcon: "empty",
|
||||
enabled: isNotRoot && parentNotSearch && noSelectedNotes},
|
||||
{ title: "Advanced", uiIcon: "empty", enabled: true, items: [
|
||||
{ title: 'Collapse subtree <kbd data-command="collapseSubtree"></kbd>', command: "collapseSubtree", uiIcon: "align-justify", enabled: noSelectedNotes },
|
||||
{ title: 'Expand subtree <kbd data-command="expandSubtree"></kbd>', command: "expandSubtree", uiIcon: "expand", enabled: noSelectedNotes },
|
||||
{ title: 'Collapse subtree <kbd data-command="collapseSubtree"></kbd>', command: "collapseSubtree", uiIcon: "collapse", enabled: noSelectedNotes },
|
||||
{ title: "Force note sync", command: "forceNoteSync", uiIcon: "refresh", enabled: noSelectedNotes },
|
||||
{ title: 'Sort alphabetically <kbd data-command="sortChildNotes"></kbd>', command: "sortChildNotes", uiIcon: "empty", enabled: noSelectedNotes && notSearch },
|
||||
{ title: 'Recent changes in subtree', command: "recentChangesInSubtree", uiIcon: "history", enabled: noSelectedNotes }
|
||||
@@ -129,7 +131,7 @@ class TreeContextMenu {
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.treeWidget.triggerCommand(command, {node: this.node});
|
||||
this.treeWidget.triggerCommand(command, {node: this.node, notePath: notePath});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,10 @@ function formatDateTime(date) {
|
||||
return formatDate(date) + " " + formatTime(date);
|
||||
}
|
||||
|
||||
function localNowDateTime() {
|
||||
return dayjs().format('YYYY-MM-DD HH:mm:ss.SSSZZ')
|
||||
}
|
||||
|
||||
function now() {
|
||||
return formatTimeWithSeconds(new Date());
|
||||
}
|
||||
@@ -99,7 +103,7 @@ function download(url) {
|
||||
url += '?' + Date.now(); // don't use cache
|
||||
|
||||
if (isElectron()) {
|
||||
const remote = utils.dynamicRequire('electron').remote;
|
||||
const remote = dynamicRequire('electron').remote;
|
||||
|
||||
remote.getCurrentWebContents().downloadURL(url);
|
||||
}
|
||||
@@ -239,7 +243,7 @@ function focusSavedElement() {
|
||||
$lastFocusedElement = null;
|
||||
}
|
||||
|
||||
function openDialog($dialog) {
|
||||
async function openDialog($dialog) {
|
||||
closeActiveDialog();
|
||||
|
||||
glob.activeDialog = $dialog;
|
||||
@@ -253,6 +257,9 @@ function openDialog($dialog) {
|
||||
focusSavedElement();
|
||||
}
|
||||
});
|
||||
|
||||
const keyboardActionsService = (await import("./keyboard_actions.js")).default;
|
||||
keyboardActionsService.updateDisplayedShortcuts($dialog);
|
||||
}
|
||||
|
||||
function isHtmlEmpty(html) {
|
||||
@@ -270,7 +277,7 @@ function isHtmlEmpty(html) {
|
||||
|
||||
async function clearBrowserCache() {
|
||||
if (isElectron()) {
|
||||
const win = utils.dynamicRequire('electron').remote.getCurrentWindow();
|
||||
const win = dynamicRequire('electron').remote.getCurrentWindow();
|
||||
await win.webContents.session.clearCache();
|
||||
}
|
||||
}
|
||||
@@ -309,6 +316,24 @@ function dynamicRequire(moduleName) {
|
||||
}
|
||||
}
|
||||
|
||||
function timeLimit(promise, limitMs) {
|
||||
return new Promise((res, rej) => {
|
||||
let resolved = false;
|
||||
|
||||
promise.then(result => {
|
||||
resolved = true;
|
||||
|
||||
res(result);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
if (!resolved) {
|
||||
rej(new Error('Process exceeded time limit ' + limitMs));
|
||||
}
|
||||
}, limitMs);
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
reloadApp,
|
||||
parseDate,
|
||||
@@ -318,6 +343,7 @@ export default {
|
||||
formatDate,
|
||||
formatDateISO,
|
||||
formatDateTime,
|
||||
localNowDateTime,
|
||||
now,
|
||||
isElectron,
|
||||
isMac,
|
||||
@@ -347,5 +373,6 @@ export default {
|
||||
normalizeShortcut,
|
||||
copySelectionToClipboard,
|
||||
isCKEditorInitialized,
|
||||
dynamicRequire
|
||||
};
|
||||
dynamicRequire,
|
||||
timeLimit
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import Branch from "../entities/branch.js";
|
||||
import Attribute from "../entities/attribute.js";
|
||||
import options from "./options.js";
|
||||
import treeCache from "./tree_cache.js";
|
||||
import noteAttributeCache from "./note_attribute_cache.js";
|
||||
|
||||
const $outstandingSyncsCount = $("#outstanding-syncs-count");
|
||||
|
||||
@@ -24,7 +25,8 @@ function logError(message) {
|
||||
if (ws && ws.readyState === 1) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'log-error',
|
||||
error: message
|
||||
error: message,
|
||||
stack: new Error().stack
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -36,6 +38,21 @@ function subscribeToMessages(messageHandler) {
|
||||
// used to serialize sync operations
|
||||
let consumeQueuePromise = null;
|
||||
|
||||
// most sync events are sent twice - once immediatelly after finishing the transaction and once during the scheduled ping
|
||||
// but we want to process only once
|
||||
const processedSyncIds = new Set();
|
||||
|
||||
function logRows(syncRows) {
|
||||
const filteredRows = syncRows.filter(row =>
|
||||
!processedSyncIds.has(row.id)
|
||||
&& row.entityName !== 'recent_notes'
|
||||
&& (row.entityName !== 'options' || row.entityId !== 'openTabs'));
|
||||
|
||||
if (filteredRows.length > 0) {
|
||||
console.debug(utils.now(), "Sync data: ", filteredRows);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMessage(event) {
|
||||
const message = JSON.parse(event.data);
|
||||
|
||||
@@ -50,13 +67,7 @@ async function handleMessage(event) {
|
||||
$outstandingSyncsCount.html(message.outstandingSyncs);
|
||||
|
||||
if (syncRows.length > 0) {
|
||||
const filteredRows = syncRows.filter(row =>
|
||||
row.entityName !== 'recent_notes'
|
||||
&& (row.entityName !== 'options' || row.entityId !== 'openTabs'));
|
||||
|
||||
if (filteredRows.length > 0) {
|
||||
console.debug(utils.now(), "Sync data: ", filteredRows);
|
||||
}
|
||||
logRows(syncRows);
|
||||
|
||||
syncDataQueue.push(...syncRows);
|
||||
|
||||
@@ -132,13 +143,21 @@ async function runSafely(syncHandler, syncData) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: we should rethink the fact that each sync row is sent twice (once at the end of transaction, once periodically)
|
||||
* and we keep both lastProcessedSyncId and processedSyncIds
|
||||
* it even seems incorrect that when transaction sync rows are received, we incorrectly increase lastProcessedSyncId
|
||||
* and then some syncs might lost (or are *all* sync rows sent from transactions?)
|
||||
*/
|
||||
async function consumeSyncData() {
|
||||
if (syncDataQueue.length > 0) {
|
||||
const allSyncData = syncDataQueue;
|
||||
const allSyncRows = syncDataQueue;
|
||||
syncDataQueue = [];
|
||||
|
||||
const nonProcessedSyncRows = allSyncRows.filter(sync => !processedSyncIds.has(sync.id));
|
||||
|
||||
try {
|
||||
await processSyncRows(allSyncData);
|
||||
await utils.timeLimit(processSyncRows(nonProcessedSyncRows), 5000);
|
||||
}
|
||||
catch (e) {
|
||||
logError(`Encountered error ${e.message}: ${e.stack}, reloading frontend.`);
|
||||
@@ -147,7 +166,11 @@ async function consumeSyncData() {
|
||||
utils.reloadApp();
|
||||
}
|
||||
|
||||
lastProcessedSyncId = Math.max(lastProcessedSyncId, allSyncData[allSyncData.length - 1].id);
|
||||
for (const syncRow of nonProcessedSyncRows) {
|
||||
processedSyncIds.add(syncRow.id);
|
||||
}
|
||||
|
||||
lastProcessedSyncId = Math.max(lastProcessedSyncId, allSyncRows[allSyncRows.length - 1].id);
|
||||
}
|
||||
|
||||
checkSyncIdListeners();
|
||||
@@ -169,7 +192,7 @@ function connectWebSocket() {
|
||||
|
||||
async function sendPing() {
|
||||
if (Date.now() - lastPingTs > 30000) {
|
||||
console.log(utils.now(), "Lost websocket connection to the backend");
|
||||
console.log(utils.now(), "Lost websocket connection to the backend. If you keep having this issue repeatedly, you might want to check your reverse proxy (nginx, apache) configuration and allow/unblock WebSocket.");
|
||||
}
|
||||
|
||||
if (ws.readyState === ws.OPEN) {
|
||||
@@ -211,18 +234,18 @@ subscribeToMessages(message => {
|
||||
async function processSyncRows(syncRows) {
|
||||
const missingNoteIds = [];
|
||||
|
||||
syncRows.forEach(({entityName, entity}) => {
|
||||
if (entityName === 'branches' && !(entity.parentNoteId in treeCache.notes)) {
|
||||
for (const {entityName, entity} of syncRows) {
|
||||
if (entityName === 'branches' && !(entity.parentNoteId in treeCache.notes)) {
|
||||
missingNoteIds.push(entity.parentNoteId);
|
||||
}
|
||||
else if (entityName === 'attributes'
|
||||
&& entity.type === 'relation'
|
||||
&& entity.name === 'template'
|
||||
&& !(entity.noteId in treeCache.notes)) {
|
||||
}
|
||||
else if (entityName === 'attributes'
|
||||
&& entity.type === 'relation'
|
||||
&& entity.name === 'template'
|
||||
&& !(entity.noteId in treeCache.notes)) {
|
||||
|
||||
missingNoteIds.push(entity.value);
|
||||
}
|
||||
});
|
||||
missingNoteIds.push(entity.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingNoteIds.length > 0) {
|
||||
await treeCache.reloadNotes(missingNoteIds);
|
||||
@@ -230,16 +253,16 @@ async function processSyncRows(syncRows) {
|
||||
|
||||
const loadResults = new LoadResults(treeCache);
|
||||
|
||||
syncRows.filter(sync => sync.entityName === 'notes').forEach(sync => {
|
||||
for (const sync of syncRows.filter(sync => sync.entityName === 'notes')) {
|
||||
const note = treeCache.notes[sync.entityId];
|
||||
|
||||
if (note) {
|
||||
note.update(sync.entity);
|
||||
loadResults.addNote(sync.entityId, sync.sourceId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
syncRows.filter(sync => sync.entityName === 'branches').forEach(sync => {
|
||||
for (const sync of syncRows.filter(sync => sync.entityName === 'branches')) {
|
||||
let branch = treeCache.branches[sync.entityId];
|
||||
const childNote = treeCache.notes[sync.entity.noteId];
|
||||
const parentNote = treeCache.notes[sync.entity.parentNoteId];
|
||||
@@ -285,9 +308,9 @@ async function processSyncRows(syncRows) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
syncRows.filter(sync => sync.entityName === 'note_reordering').forEach(sync => {
|
||||
for (const sync of syncRows.filter(sync => sync.entityName === 'note_reordering')) {
|
||||
for (const branchId in sync.positions) {
|
||||
const branch = treeCache.branches[branchId];
|
||||
|
||||
@@ -297,10 +320,10 @@ async function processSyncRows(syncRows) {
|
||||
}
|
||||
|
||||
loadResults.addNoteReordering(sync.entityId, sync.sourceId);
|
||||
});
|
||||
}
|
||||
|
||||
// missing reloading the relation target note
|
||||
syncRows.filter(sync => sync.entityName === 'attributes').forEach(sync => {
|
||||
for (const sync of syncRows.filter(sync => sync.entityName === 'attributes')) {
|
||||
let attribute = treeCache.attributes[sync.entityId];
|
||||
const sourceNote = treeCache.notes[sync.entity.noteId];
|
||||
const targetNote = sync.entity.type === 'relation' && treeCache.notes[sync.entity.value];
|
||||
@@ -336,29 +359,33 @@ async function processSyncRows(syncRows) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
syncRows.filter(sync => sync.entityName === 'note_contents').forEach(sync => {
|
||||
for (const sync of syncRows.filter(sync => sync.entityName === 'note_contents')) {
|
||||
delete treeCache.noteComplementPromises[sync.entityId];
|
||||
|
||||
loadResults.addNoteContent(sync.entityId, sync.sourceId);
|
||||
});
|
||||
}
|
||||
|
||||
syncRows.filter(sync => sync.entityName === 'note_revisions').forEach(sync => {
|
||||
for (const sync of syncRows.filter(sync => sync.entityName === 'note_revisions')) {
|
||||
loadResults.addNoteRevision(sync.entityId, sync.noteId, sync.sourceId);
|
||||
});
|
||||
}
|
||||
|
||||
syncRows.filter(sync => sync.entityName === 'options').forEach(sync => {
|
||||
for (const sync of syncRows.filter(sync => sync.entityName === 'options')) {
|
||||
if (sync.entity.name === 'openTabs') {
|
||||
return; // only noise
|
||||
continue; // only noise
|
||||
}
|
||||
|
||||
options.set(sync.entity.name, sync.entity.value);
|
||||
|
||||
loadResults.addOption(sync.entity.name);
|
||||
});
|
||||
}
|
||||
|
||||
if (!loadResults.isEmpty()) {
|
||||
if (loadResults.hasAttributeRelatedChanges()) {
|
||||
noteAttributeCache.invalidate();
|
||||
}
|
||||
|
||||
const appContext = (await import("./app_context.js")).default;
|
||||
await appContext.triggerEvent('entitiesReloaded', {loadResults});
|
||||
}
|
||||
@@ -369,4 +396,4 @@ export default {
|
||||
subscribeToMessages,
|
||||
waitForSyncId,
|
||||
waitForMaxKnownSyncId
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,31 +5,35 @@ import utils from "../services/utils.js";
|
||||
const MIN_ZOOM = 0.5;
|
||||
const MAX_ZOOM = 2.0;
|
||||
|
||||
export default class ZoomService extends Component {
|
||||
class ZoomService extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.setZoomFactor(options.getFloat('zoomFactor'));
|
||||
if (utils.isElectron()) {
|
||||
options.initializedPromise.then(() => {
|
||||
this.setZoomFactor(options.getFloat('zoomFactor'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setZoomFactor(zoomFactor) {
|
||||
zoomFactor = parseFloat(zoomFactor);
|
||||
|
||||
|
||||
const webFrame = utils.dynamicRequire('electron').webFrame;
|
||||
webFrame.setZoomFactor(zoomFactor);
|
||||
}
|
||||
|
||||
|
||||
async setZoomFactorAndSave(zoomFactor) {
|
||||
if (zoomFactor >= MIN_ZOOM && zoomFactor <= MAX_ZOOM) {
|
||||
this.setZoomFactor(zoomFactor);
|
||||
|
||||
|
||||
await options.save('zoomFactor', zoomFactor);
|
||||
}
|
||||
else {
|
||||
console.log(`Zoom factor ${zoomFactor} outside of the range, ignored.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getCurrentZoom() {
|
||||
return utils.dynamicRequire('electron').webFrame.getZoomFactor();
|
||||
}
|
||||
@@ -45,4 +49,8 @@ export default class ZoomService extends Component {
|
||||
setZoomFactorAndSaveEvent({zoomFactor}) {
|
||||
this.setZoomFactorAndSave(zoomFactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const zoomService = new ZoomService();
|
||||
|
||||
export default zoomService;
|
||||
|
||||
@@ -30,6 +30,11 @@ class BasicWidget extends Component {
|
||||
return this;
|
||||
}
|
||||
|
||||
filling() {
|
||||
this.css('flex-grow', '1');
|
||||
return this;
|
||||
}
|
||||
|
||||
hideInZenMode() {
|
||||
this.class('hide-in-zen-mode');
|
||||
return this;
|
||||
@@ -57,7 +62,10 @@ class BasicWidget extends Component {
|
||||
for (const key in this.attrs) {
|
||||
if (key === 'style') {
|
||||
if (this.attrs[key]) {
|
||||
$widget.attr(key, $widget.attr('style') + ';' + this.attrs[key]);
|
||||
let style = $widget.attr('style');
|
||||
style = style ? `${style}; ${this.attrs[key]}` : this.attrs[key];
|
||||
|
||||
$widget.attr(key, style);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -106,4 +114,4 @@ class BasicWidget extends Component {
|
||||
cleanup() {}
|
||||
}
|
||||
|
||||
export default BasicWidget;
|
||||
export default BasicWidget;
|
||||
|
||||
@@ -34,7 +34,8 @@ export default class CollapsibleWidget extends TabAwareWidget {
|
||||
this.$bodyWrapper = this.$widget.find('.body-wrapper');
|
||||
this.$bodyWrapper.attr('id', this.componentId); // for toggle to work we need id
|
||||
|
||||
this.widgetName = this.constructor.name;
|
||||
// not using constructor name because of webpack mangling class names ...
|
||||
this.widgetName = this.widgetTitle.replace(/[^[a-zA-Z0-9]/g, "_");
|
||||
|
||||
if (!options.is(this.widgetName + 'Collapsed')) {
|
||||
this.$bodyWrapper.collapse("show");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import utils from "../services/utils.js";
|
||||
import linkService from "../services/link.js";
|
||||
import ws from "../services/ws.js";
|
||||
import CollapsibleWidget from "./collapsible_widget.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import linkService from "../../services/link.js";
|
||||
import ws from "../../services/ws.js";
|
||||
import CollapsibleWidget from "../collapsible_widget.js";
|
||||
|
||||
export default class AttributesWidget extends CollapsibleWidget {
|
||||
get widgetTitle() { return "Attributes"; }
|
||||
@@ -16,7 +16,7 @@ export default class AttributesWidget extends CollapsibleWidget {
|
||||
get headerActions() {
|
||||
const $showFullButton = $("<a>").append("show dialog").addClass('widget-header-action');
|
||||
$showFullButton.on('click', async () => {
|
||||
const attributesDialog = await import("../dialogs/attributes.js");
|
||||
const attributesDialog = await import("../../dialogs/attributes.js");
|
||||
attributesDialog.showDialog();
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import CollapsibleWidget from "./collapsible_widget.js";
|
||||
import libraryLoader from "../services/library_loader.js";
|
||||
import utils from "../services/utils.js";
|
||||
import dateNoteService from "../services/date_notes.js";
|
||||
import server from "../services/server.js";
|
||||
import appContext from "../services/app_context.js";
|
||||
import CollapsibleWidget from "../collapsible_widget.js";
|
||||
import libraryLoader from "../../services/library_loader.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import dateNoteService from "../../services/date_notes.js";
|
||||
import server from "../../services/server.js";
|
||||
import appContext from "../../services/app_context.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="calendar-widget">
|
||||
@@ -1,7 +1,7 @@
|
||||
import CollapsibleWidget from "./collapsible_widget.js";
|
||||
import linkService from "../services/link.js";
|
||||
import server from "../services/server.js";
|
||||
import treeCache from "../services/tree_cache.js";
|
||||
import CollapsibleWidget from "../collapsible_widget.js";
|
||||
import linkService from "../../services/link.js";
|
||||
import server from "../../services/server.js";
|
||||
import treeCache from "../../services/tree_cache.js";
|
||||
|
||||
export default class EditedNotesWidget extends CollapsibleWidget {
|
||||
get widgetTitle() { return "Edited notes on this day"; }
|
||||
@@ -1,4 +1,4 @@
|
||||
import CollapsibleWidget from "./collapsible_widget.js";
|
||||
import CollapsibleWidget from "../collapsible_widget.js";
|
||||
|
||||
let linkMapContainerIdCtr = 1;
|
||||
|
||||
@@ -21,7 +21,7 @@ export default class LinkMapWidget extends CollapsibleWidget {
|
||||
get headerActions() {
|
||||
const $showFullButton = $("<a>").append("show full").addClass('widget-header-action');
|
||||
$showFullButton.on('click', async () => {
|
||||
const linkMapDialog = await import("../dialogs/link_map.js");
|
||||
const linkMapDialog = await import("../../dialogs/link_map.js");
|
||||
linkMapDialog.showDialog();
|
||||
});
|
||||
|
||||
@@ -66,7 +66,7 @@ export default class LinkMapWidget extends CollapsibleWidget {
|
||||
const $linkMapContainer = this.$body.find('.link-map-container');
|
||||
$linkMapContainer.attr("id", "link-map-container-" + linkMapContainerIdCtr++);
|
||||
|
||||
const LinkMapServiceClass = (await import('../services/link_map.js')).default;
|
||||
const LinkMapServiceClass = (await import('../../services/link_map.js')).default;
|
||||
|
||||
this.linkMapService = new LinkMapServiceClass(note, $linkMapContainer, {
|
||||
maxDepth: 1,
|
||||
@@ -1,10 +1,9 @@
|
||||
import CollapsibleWidget from "./collapsible_widget.js";
|
||||
import CollapsibleWidget from "../collapsible_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<table class="note-info-widget-table">
|
||||
<style>
|
||||
.note-info-widget-table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -21,23 +20,24 @@ const TPL = `
|
||||
</style>
|
||||
|
||||
<tr>
|
||||
<th nowrap>Note ID:</th>
|
||||
<td nowrap colspan="3" class="note-info-note-id"></td>
|
||||
<th>Note ID:</th>
|
||||
<td class="note-info-note-id"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th nowrap>Created:</th>
|
||||
<td nowrap colspan="3" style="overflow: hidden; text-overflow: ellipsis;" class="note-info-date-created"></td>
|
||||
<th>Created:</th>
|
||||
<td class="note-info-date-created"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th nowrap>Modified:</th>
|
||||
<td nowrap colspan="3" style="overflow: hidden; text-overflow: ellipsis;" class="note-info-date-modified"></td>
|
||||
<th>Modified:</th>
|
||||
<td class="note-info-date-modified"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Type:</th>
|
||||
<td class="note-info-type"></td>
|
||||
|
||||
<th>MIME:</th>
|
||||
<td class="note-info-mime"></td>
|
||||
<td>
|
||||
<span class="note-info-type"></span>
|
||||
|
||||
<span class="note-info-mime"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
`;
|
||||
@@ -60,18 +60,21 @@ export default class NoteInfoWidget extends CollapsibleWidget {
|
||||
|
||||
this.$noteId.text(note.noteId);
|
||||
this.$dateCreated
|
||||
.text(noteComplement.dateCreated)
|
||||
.text(noteComplement.dateCreated.substr(0, 16))
|
||||
.attr("title", noteComplement.dateCreated);
|
||||
|
||||
this.$dateModified
|
||||
.text(noteComplement.dateModified)
|
||||
.text(noteComplement.dateModified.substr(0, 16))
|
||||
.attr("title", noteComplement.dateCreated);
|
||||
|
||||
this.$type.text(note.type);
|
||||
|
||||
this.$mime
|
||||
.text(note.mime)
|
||||
.attr("title", note.mime);
|
||||
if (note.mime) {
|
||||
this.$mime.text('(' + note.mime + ')');
|
||||
}
|
||||
else {
|
||||
this.$mime.empty();
|
||||
}
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({loadResults}) {
|
||||
@@ -79,4 +82,4 @@ export default class NoteInfoWidget extends CollapsibleWidget {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import server from "../services/server.js";
|
||||
import CollapsibleWidget from "./collapsible_widget.js";
|
||||
import server from "../../services/server.js";
|
||||
import CollapsibleWidget from "../collapsible_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<ul class="note-revision-list" style="max-height: 150px; overflow: auto;">
|
||||
@@ -19,7 +19,7 @@ class NoteRevisionsWidget extends CollapsibleWidget {
|
||||
get headerActions() {
|
||||
const $showFullButton = $("<a>").append("show dialog").addClass('widget-header-action');
|
||||
$showFullButton.on('click', async () => {
|
||||
const attributesDialog = await import("../dialogs/note_revisions.js");
|
||||
const attributesDialog = await import("../../dialogs/note_revisions.js");
|
||||
attributesDialog.showCurrentNoteRevisions(this.noteId);
|
||||
});
|
||||
|
||||
@@ -59,6 +59,7 @@ class NoteRevisionsWidget extends CollapsibleWidget {
|
||||
'data-action': 'note-revision',
|
||||
'data-note-path': note.noteId,
|
||||
'data-note-revision-id': item.noteRevisionId,
|
||||
title: 'This revision was last edited on ' + item.dateLastEdited,
|
||||
href: 'javascript:'
|
||||
}).text(item.dateLastEdited.substr(0, 16)));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import CollapsibleWidget from "./collapsible_widget.js";
|
||||
import linkService from "../services/link.js";
|
||||
import server from "../services/server.js";
|
||||
import treeCache from "../services/tree_cache.js";
|
||||
import CollapsibleWidget from "../collapsible_widget.js";
|
||||
import linkService from "../../services/link.js";
|
||||
import server from "../../services/server.js";
|
||||
import treeCache from "../../services/tree_cache.js";
|
||||
|
||||
export default class SimilarNotesWidget extends CollapsibleWidget {
|
||||
get widgetTitle() { return "Similar notes"; }
|
||||
@@ -1,5 +1,5 @@
|
||||
import CollapsibleWidget from "./collapsible_widget.js";
|
||||
import linkService from "../services/link.js";
|
||||
import CollapsibleWidget from "../collapsible_widget.js";
|
||||
import linkService from "../../services/link.js";
|
||||
|
||||
export default class WhatLinksHereWidget extends CollapsibleWidget {
|
||||
get widgetTitle() { return "What links here"; }
|
||||
@@ -13,7 +13,7 @@ export default class WhatLinksHereWidget extends CollapsibleWidget {
|
||||
get headerActions() {
|
||||
const $showFullButton = $("<a>").append("show link map").addClass('widget-header-action');
|
||||
$showFullButton.on('click', async () => {
|
||||
const linkMapDialog = await import("../dialogs/link_map.js");
|
||||
const linkMapDialog = await import("../../dialogs/link_map.js");
|
||||
linkMapDialog.showDialog();
|
||||
});
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import utils from '../services/utils.js';
|
||||
import Mutex from "../services/mutex.js";
|
||||
|
||||
/**
|
||||
* Abstract class for all components in the Trilium's frontend.
|
||||
*
|
||||
* Contains also event implementation with following properties:
|
||||
* - event / command distribution is synchronous which among others mean that events are well ordered - event
|
||||
* which was sent out first will also be processed first by the component since it was added to the mutex queue
|
||||
* as the first one
|
||||
* which was sent out first will also be processed first by the component
|
||||
* - execution of the event / command is asynchronous - each component executes the event on its own without regard for
|
||||
* other components.
|
||||
* - although the execution is async, we are collecting all the promises and therefore it is possible to wait until the
|
||||
@@ -15,11 +13,10 @@ import Mutex from "../services/mutex.js";
|
||||
*/
|
||||
export default class Component {
|
||||
constructor() {
|
||||
this.componentId = `comp-${this.constructor.name}-` + utils.randomString(6);
|
||||
this.componentId = `comp-` + utils.randomString(8);
|
||||
/** @type Component[] */
|
||||
this.children = [];
|
||||
this.initialized = Promise.resolve();
|
||||
this.mutex = new Mutex();
|
||||
}
|
||||
|
||||
setParent(parent) {
|
||||
@@ -79,18 +76,8 @@ export default class Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
let release;
|
||||
await fun.call(this, data);
|
||||
|
||||
try {
|
||||
release = await this.mutex.acquire();
|
||||
|
||||
await fun.call(this, data);
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
if (release) {
|
||||
release();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,17 +18,14 @@ const WIDGET_TPL = `
|
||||
|
||||
<a data-trigger-command="collapseTree"
|
||||
title="Collapse note tree"
|
||||
data-command="collapseTree"
|
||||
class="icon-action bx bx-layer-minus"></a>
|
||||
|
||||
<a data-trigger-command="scrollToActiveNote"
|
||||
title="Scroll to active note"
|
||||
data-command="scrollToActiveNote"
|
||||
title="Scroll to active note"
|
||||
class="icon-action bx bx-crosshair"></a>
|
||||
|
||||
<a data-trigger-command="searchNotes"
|
||||
title="Search in notes"
|
||||
data-command="searchNotes"
|
||||
class="icon-action bx bx-search"></a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import BasicWidget from "./basic_widget.js";
|
||||
import keyboardActionService from "../services/keyboard_actions.js";
|
||||
import utils from "../services/utils.js";
|
||||
import syncService from "../services/sync.js";
|
||||
|
||||
@@ -14,7 +13,7 @@ const TPL = `
|
||||
.global-menu button {
|
||||
margin-right: 10px;
|
||||
height: 33px;
|
||||
border-bottom: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.global-menu .dropdown-menu {
|
||||
@@ -36,7 +35,13 @@ const TPL = `
|
||||
|
||||
<a class="dropdown-item sync-now-button" title="Trigger sync">
|
||||
<span class="bx bx-refresh"></span>
|
||||
Sync (<span id="outstanding-syncs-count">0</span>)
|
||||
Sync now (<span id="outstanding-syncs-count">0</span>)
|
||||
</a>
|
||||
|
||||
<a class="dropdown-item" data-trigger-command="openNewWindow">
|
||||
<span class="bx bx-window-open"></span>
|
||||
Open new window
|
||||
<kbd data-command="openNewWindow"></kbd>
|
||||
</a>
|
||||
|
||||
<a class="dropdown-item open-dev-tools-button" data-trigger-command="openDevTools">
|
||||
@@ -111,4 +116,4 @@ export default class GlobalMenuWidget extends BasicWidget {
|
||||
|
||||
return this.$widget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import BasicWidget from "./basic_widget.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<button type="button" class="action-button d-sm-none d-md-none d-lg-none d-xl-none" aria-label="Close">
|
||||
@@ -1,8 +1,8 @@
|
||||
import BasicWidget from "./basic_widget.js";
|
||||
import appContext from "../services/app_context.js";
|
||||
import contextMenu from "../services/context_menu.js";
|
||||
import noteCreateService from "../services/note_create.js";
|
||||
import branchService from "../services/branches.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import appContext from "../../services/app_context.js";
|
||||
import contextMenu from "../../services/context_menu.js";
|
||||
import noteCreateService from "../../services/note_create.js";
|
||||
import branchService from "../../services/branches.js";
|
||||
|
||||
const TPL = `<button type="button" class="action-button bx bx-menu"></button>`;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import BasicWidget from "./basic_widget.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
|
||||
const WIDGET_TPL = `
|
||||
<div id="global-buttons">
|
||||
@@ -1,4 +1,4 @@
|
||||
import Component from "./component.js";
|
||||
import Component from "../component.js";
|
||||
|
||||
export default class MobileScreenSwitcherExecutor extends Component {
|
||||
setActiveScreenCommand({screen}) {
|
||||
@@ -1,4 +1,4 @@
|
||||
import FlexContainer from "./flex_container.js";
|
||||
import FlexContainer from "../flex_container.js";
|
||||
|
||||
export default class ScreenContainer extends FlexContainer {
|
||||
constructor(screenName, direction) {
|
||||
@@ -1,4 +1,5 @@
|
||||
import TabAwareWidget from "./tab_aware_widget.js";
|
||||
import protectedSessionService from "../services/protected_session.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="dropdown note-actions">
|
||||
@@ -12,6 +13,49 @@ const TPL = `
|
||||
background-color: transparent !important;
|
||||
pointer-events: none; /* makes it unclickable */
|
||||
}
|
||||
|
||||
/* The switch - the box around the slider */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* The slider */
|
||||
.slider {
|
||||
border-radius: 24px;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--more-accented-background-color);
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: var(--main-background-color);
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider.checked {
|
||||
background-color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.slider.checked:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle">
|
||||
@@ -19,6 +63,22 @@ const TPL = `
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<div class="dropdown-item protect-button">
|
||||
Protect the note
|
||||
|
||||
<span title="Note is not protected, click to make it protected">
|
||||
<label class="switch">
|
||||
<span class="slider"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="dropdown-item unprotect-button">
|
||||
Unprotect the note
|
||||
|
||||
<span title="Note is protected, click to make it unprotected">
|
||||
<label class="switch">
|
||||
<span class="slider checked"></span>
|
||||
</span>
|
||||
</div>
|
||||
<a data-trigger-command="showNoteRevisions" class="dropdown-item show-note-revisions-button">Revisions</a>
|
||||
<a data-trigger-command="showAttributes" class="dropdown-item show-attributes-button"><kbd data-command="showAttributes"></kbd> Attributes</a>
|
||||
<a data-trigger-command="showLinkMap" class="dropdown-item show-link-map-button"><kbd data-command="showLinkMap"></kbd> Link map</a>
|
||||
@@ -48,6 +108,12 @@ export default class NoteActionsWidget extends TabAwareWidget {
|
||||
this.$importNoteButton = this.$widget.find('.import-files-button');
|
||||
this.$importNoteButton.on("click", () => import('../dialogs/import.js').then(d => d.showDialog(this.noteId)));
|
||||
|
||||
this.$protectButton = this.$widget.find(".protect-button");
|
||||
this.$protectButton.on('click', () => protectedSessionService.protectNote(this.noteId, true, false));
|
||||
|
||||
this.$unprotectButton = this.$widget.find(".unprotect-button");
|
||||
this.$unprotectButton.on('click', () => protectedSessionService.protectNote(this.noteId, false, false));
|
||||
|
||||
return this.$widget;
|
||||
}
|
||||
|
||||
@@ -64,5 +130,14 @@ export default class NoteActionsWidget extends TabAwareWidget {
|
||||
else {
|
||||
this.$exportNoteButton.attr('disabled', 'disabled');
|
||||
}
|
||||
|
||||
this.$protectButton.toggle(!note.isProtected);
|
||||
this.$unprotectButton.toggle(!!note.isProtected);
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({loadResults}) {
|
||||
if (loadResults.isNoteReloaded(this.noteId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import server from "../services/server.js";
|
||||
import libraryLoader from "../services/library_loader.js";
|
||||
import EmptyTypeWidget from "./type_widgets/empty.js";
|
||||
import EditableTextTypeWidget from "./type_widgets/editable_text.js";
|
||||
import CodeTypeWidget from "./type_widgets/code.js";
|
||||
import EditableCodeTypeWidget from "./type_widgets/editable_code.js";
|
||||
import FileTypeWidget from "./type_widgets/file.js";
|
||||
import ImageTypeWidget from "./type_widgets/image.js";
|
||||
import SearchTypeWidget from "./type_widgets/search.js";
|
||||
@@ -19,6 +19,7 @@ import keyboardActionsService from "../services/keyboard_actions.js";
|
||||
import noteCreateService from "../services/note_create.js";
|
||||
import DeletedTypeWidget from "./type_widgets/deleted.js";
|
||||
import ReadOnlyTextTypeWidget from "./type_widgets/read_only_text.js";
|
||||
import ReadOnlyCodeTypeWidget from "./type_widgets/read_only_code.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="note-detail">
|
||||
@@ -38,7 +39,8 @@ const typeWidgetClasses = {
|
||||
'deleted': DeletedTypeWidget,
|
||||
'editable-text': EditableTextTypeWidget,
|
||||
'read-only-text': ReadOnlyTextTypeWidget,
|
||||
'code': CodeTypeWidget,
|
||||
'editable-code': EditableCodeTypeWidget,
|
||||
'read-only-code': ReadOnlyCodeTypeWidget,
|
||||
'file': FileTypeWidget,
|
||||
'image': ImageTypeWidget,
|
||||
'search': SearchTypeWidget,
|
||||
@@ -61,6 +63,8 @@ export default class NoteDetailWidget extends TabAwareWidget {
|
||||
const dto = note.dto;
|
||||
dto.content = this.getTypeWidget().getContent();
|
||||
|
||||
protectedSessionHolder.touchProtectedSessionIfNecessary(note);
|
||||
|
||||
await server.put('notes/' + noteId, dto, this.componentId);
|
||||
});
|
||||
}
|
||||
@@ -182,15 +186,32 @@ export default class NoteDetailWidget extends TabAwareWidget {
|
||||
const noteComplement = await this.tabContext.getNoteComplement();
|
||||
|
||||
if (note.hasLabel('readOnly') ||
|
||||
(noteComplement.content && noteComplement.content.length > 10000)) {
|
||||
(noteComplement.content
|
||||
&& noteComplement.content.length > 10000)
|
||||
&& !note.hasLabel('autoReadOnlyDisabled')) {
|
||||
type = 'read-only-text';
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'code' && !this.tabContext.codePreviewDisabled) {
|
||||
const noteComplement = await this.tabContext.getNoteComplement();
|
||||
|
||||
if (note.hasLabel('readOnly') ||
|
||||
(noteComplement.content
|
||||
&& noteComplement.content.length > 30000)
|
||||
&& !note.hasLabel('autoReadOnlyDisabled')) {
|
||||
type = 'read-only-code';
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'text') {
|
||||
type = 'editable-text';
|
||||
}
|
||||
|
||||
if (type === 'code') {
|
||||
type = 'editable-code';
|
||||
}
|
||||
|
||||
if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||
type = 'protected-session';
|
||||
}
|
||||
@@ -249,13 +270,30 @@ export default class NoteDetailWidget extends TabAwareWidget {
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({loadResults}) {
|
||||
// FIXME: we should test what happens when the loaded note is deleted
|
||||
|
||||
if (loadResults.isNoteContentReloaded(this.noteId, this.componentId)
|
||||
|| (loadResults.isNoteReloaded(this.noteId, this.componentId) && (this.type !== await this.getWidgetType() || this.mime !== this.note.mime))) {
|
||||
|
||||
this.handleEvent('noteTypeMimeChanged', {noteId: this.noteId});
|
||||
}
|
||||
else {
|
||||
const attrs = loadResults.getAttributes();
|
||||
|
||||
const label = attrs.find(attr =>
|
||||
attr.type === 'label'
|
||||
&& ['readOnly', 'autoReadOnlyDisabled', 'cssClass', 'bookZoomLevel'].includes(attr.name)
|
||||
&& attr.isAffecting(this.note));
|
||||
|
||||
const relation = attrs.find(attr =>
|
||||
attr.type === 'relation'
|
||||
&& ['template', 'renderNote'].includes(attr.name)
|
||||
&& attr.isAffecting(this.note));
|
||||
|
||||
if (label || relation) {
|
||||
// probably incorrect event
|
||||
// calling this.refresh() is not enough since the event needs to be propagated to children as well
|
||||
this.handleEvent('noteTypeMimeChanged', {noteId: this.noteId});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beforeUnloadEvent() {
|
||||
@@ -274,6 +312,12 @@ export default class NoteDetailWidget extends TabAwareWidget {
|
||||
}
|
||||
}
|
||||
|
||||
codePreviewDisabledEvent({tabContext}) {
|
||||
if (this.isTab(tabContext.tabId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async cutIntoNoteCommand() {
|
||||
const note = appContext.tabManager.getActiveTabNote();
|
||||
|
||||
@@ -281,9 +325,10 @@ export default class NoteDetailWidget extends TabAwareWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
await noteCreateService.createNote(note.noteId, {
|
||||
// without await as this otherwise causes deadlock through component mutex
|
||||
noteCreateService.createNote(note.noteId, {
|
||||
isProtected: note.isProtected,
|
||||
saveSelection: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ export default class NotePathsWidget extends TabAwareWidget {
|
||||
this.$currentPath.append(
|
||||
$("<a>")
|
||||
.attr('href', '#' + curPath)
|
||||
.attr('data-note-path', curPath)
|
||||
.addClass('no-tooltip-preview')
|
||||
.text(await treeService.getNoteTitle(noteId, parentNoteId))
|
||||
);
|
||||
|
||||
@@ -17,6 +17,10 @@ const TPL = `
|
||||
min-width: 5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.note-title-container input.note-title.protected {
|
||||
text-shadow: 4px 4px 4px var(--muted-text-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<input autocomplete="off" value="" class="note-title" tabindex="1">
|
||||
@@ -29,6 +33,8 @@ export default class NoteTitleWidget extends TabAwareWidget {
|
||||
this.spacedUpdate = new SpacedUpdate(async () => {
|
||||
const title = this.$noteTitle.val();
|
||||
|
||||
protectedSessionHolder.touchProtectedSessionIfNecessary(this.note);
|
||||
|
||||
await server.put(`notes/${this.noteId}/change-title`, {title});
|
||||
});
|
||||
}
|
||||
@@ -50,6 +56,12 @@ export default class NoteTitleWidget extends TabAwareWidget {
|
||||
this.$noteTitle.val(note.title);
|
||||
|
||||
this.$noteTitle.prop("readonly", note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable());
|
||||
|
||||
this.setProtectedStatus(note);
|
||||
}
|
||||
|
||||
setProtectedStatus(note) {
|
||||
this.$noteTitle.toggleClass("protected", !!note.isProtected);
|
||||
}
|
||||
|
||||
async beforeNoteSwitchEvent({tabContext}) {
|
||||
@@ -78,7 +90,14 @@ export default class NoteTitleWidget extends TabAwareWidget {
|
||||
}
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({loadResults}) {
|
||||
if (loadResults.isNoteReloaded(this.noteId)) {
|
||||
// not updating the title specifically since the synced title might be older than what the user is currently typing
|
||||
this.setProtectedStatus(this.note);
|
||||
}
|
||||
}
|
||||
|
||||
beforeUnloadEvent() {
|
||||
this.spacedUpdate.updateNowIfNecessary();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user