mirror of
https://github.com/zadam/trilium.git
synced 2025-10-27 16:26:31 +01:00
Compare commits
136 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
443f389d73 | ||
|
|
08edc521e4 | ||
|
|
f54f6d09b0 | ||
|
|
3219441fdf | ||
|
|
1b0a2b41da | ||
|
|
582429e762 | ||
|
|
238df0fb40 | ||
|
|
89356918f1 | ||
|
|
263b65997c | ||
|
|
2b757bfccd | ||
|
|
c78ca4c9db | ||
|
|
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 | ||
|
|
56ce23fc36 | ||
|
|
cba7e5a59f | ||
|
|
86cf8f3202 |
2
.idea/dataSources.xml
generated
2
.idea/dataSources.xml
generated
@@ -1,7 +1,7 @@
|
||||
<?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>
|
||||
|
||||
@@ -1,692 +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.18">
|
||||
<root id="1">
|
||||
<ServerVersion>3.16.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>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<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>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="39" parent="7" name="IDX_attributes_noteId_index">
|
||||
<ColNames>noteId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="40" parent="7" name="IDX_attributes_name_value">
|
||||
<ColNames>name
|
||||
value</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="41" parent="7" name="IDX_attributes_value_index">
|
||||
<ColNames>value</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</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>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="55" parent="8" name="IDX_branches_noteId_parentNoteId">
|
||||
<ColNames>noteId
|
||||
parentNoteId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="56" parent="8" name="IDX_branches_parentNoteId">
|
||||
<ColNames>parentNoteId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</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>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<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>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<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>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="85" parent="11" name="IDX_note_revisions_noteId">
|
||||
<ColNames>noteId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="86" parent="11" name="IDX_note_revisions_utcDateLastEdited">
|
||||
<ColNames>utcDateLastEdited</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="87" parent="11" name="IDX_note_revisions_utcDateCreated">
|
||||
<ColNames>utcDateCreated</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="88" parent="11" name="IDX_note_revisions_dateLastEdited">
|
||||
<ColNames>dateLastEdited</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="89" parent="11" name="IDX_note_revisions_dateCreated">
|
||||
<ColNames>dateCreated</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</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>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="106" parent="12" name="IDX_notes_title">
|
||||
<ColNames>title</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="107" parent="12" name="IDX_notes_type">
|
||||
<ColNames>type</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="108" parent="12" name="IDX_notes_isDeleted">
|
||||
<ColNames>isDeleted</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="109" parent="12" name="IDX_notes_dateCreated">
|
||||
<ColNames>dateCreated</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="110" parent="12" name="IDX_notes_dateModified">
|
||||
<ColNames>dateModified</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="111" parent="12" name="IDX_notes_utcDateCreated">
|
||||
<ColNames>utcDateCreated</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="112" parent="12" name="IDX_notes_utcDateModified">
|
||||
<ColNames>utcDateModified</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</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>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<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>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<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>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="132" parent="15" name="IDX_source_ids_utcDateCreated">
|
||||
<ColNames>utcDateCreated</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</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>integer|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>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="148" parent="18" name="IDX_sync_utcSyncDate">
|
||||
<ColNames>utcSyncDate</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</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,4 +1,4 @@
|
||||
FROM node:12.16.2-alpine
|
||||
FROM node:12.16.3-alpine
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -33,5 +33,9 @@ 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\/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.
@@ -1,167 +1,20 @@
|
||||
/*
|
||||
* !!!!!!! This stylesheet is heavily modified compared to the original for similarity with in-editor look !!!!!!!
|
||||
* This is used for printing and tar HTML export
|
||||
|
||||
* CKEditor 5 (v17.0.0) content styles.
|
||||
* Generated on Fri, 13 Mar 2020 13:27:10 GMT.
|
||||
* CKEditor 5 (v19.1.1) content styles.
|
||||
* Generated on Fri, 19 Jun 2020 01:26:44 GMT.
|
||||
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/content-styles.html
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ck-highlight-marker-blue: #72cdfd;
|
||||
--ck-highlight-marker-green: #63f963;
|
||||
--ck-highlight-marker-pink: #fc7999;
|
||||
--ck-highlight-marker-yellow: #fdfd77;
|
||||
--ck-highlight-pen-green: #118800;
|
||||
--ck-highlight-pen-red: #e91313;
|
||||
--ck-highlight-marker-blue: hsl(201, 97%, 72%);
|
||||
--ck-highlight-marker-green: hsl(120, 93%, 68%);
|
||||
--ck-highlight-marker-pink: hsl(345, 96%, 73%);
|
||||
--ck-highlight-marker-yellow: hsl(60, 97%, 73%);
|
||||
--ck-highlight-pen-green: hsl(112, 100%, 27%);
|
||||
--ck-highlight-pen-red: hsl(0, 85%, 49%);
|
||||
--ck-image-style-spacing: 1.5em;
|
||||
--ck-todo-list-checkmark-size: 16px;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized img {
|
||||
width: 100%;
|
||||
}
|
||||
/* ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized > figcaption {
|
||||
display: block;
|
||||
}
|
||||
/* ckeditor5-basic-styles/theme/code.css */
|
||||
.ck-content code {
|
||||
background-color: hsla(0, 0%, 78%, 0.3);
|
||||
padding: .15em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.ck-content .image {
|
||||
display: table;
|
||||
clear: both;
|
||||
text-align: center;
|
||||
margin: 1em auto;
|
||||
}
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.ck-content .image > img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
min-width: 50px;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-side,
|
||||
.ck-content .image-style-align-left,
|
||||
.ck-content .image-style-align-center,
|
||||
.ck-content .image-style-align-right {
|
||||
max-width: 50%;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-side {
|
||||
float: right;
|
||||
margin-left: var(--ck-image-style-spacing);
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-left {
|
||||
float: left;
|
||||
margin-right: var(--ck-image-style-spacing);
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-right {
|
||||
float: right;
|
||||
margin-left: var(--ck-image-style-spacing);
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break {
|
||||
position: relative;
|
||||
clear: both;
|
||||
padding: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 2px dashed hsl(0, 0%, 77%);
|
||||
width: 100%;
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break__label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: .3em .6em;
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-radius: 2px;
|
||||
font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
|
||||
font-size: 0.75em;
|
||||
font-weight: bold;
|
||||
color: hsl(0, 0%, 20%);
|
||||
background: #fff;
|
||||
box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
/* ckeditor5-block-quote/theme/blockquote.css */
|
||||
.ck-content blockquote {
|
||||
overflow: hidden;
|
||||
padding-right: 1.5em;
|
||||
padding-left: 1.5em;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
font-style: italic;
|
||||
border-left: solid 5px hsl(0, 0%, 80%);
|
||||
}
|
||||
/* ckeditor5-block-quote/theme/blockquote.css */
|
||||
.ck-content[dir="rtl"] blockquote {
|
||||
border-left: 0;
|
||||
border-right: solid 5px hsl(0, 0%, 80%);
|
||||
}
|
||||
/* ckeditor5-media-embed/theme/mediaembed.css */
|
||||
.ck-content .media {
|
||||
clear: both;
|
||||
margin: 1em 0;
|
||||
display: block;
|
||||
min-width: 15em;
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table {
|
||||
margin: 1em auto;
|
||||
display: table;
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px double hsl(0, 0%, 70%);
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table td,
|
||||
.ck-content .table table th {
|
||||
min-width: 2em;
|
||||
padding: .4em;
|
||||
border-color: hsl(0, 0%, 75%);
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table th {
|
||||
font-weight: bold;
|
||||
background: hsla(0, 0%, 0%, 5%);
|
||||
}
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list {
|
||||
list-style: none;
|
||||
@@ -229,6 +82,95 @@
|
||||
.ck-content .todo-list .todo-list__label .todo-list__label__description {
|
||||
vertical-align: middle;
|
||||
}
|
||||
/* ckeditor5-media-embed/theme/mediaembed.css */
|
||||
.ck-content .media {
|
||||
clear: both;
|
||||
margin: 1em 0;
|
||||
display: block;
|
||||
min-width: 15em;
|
||||
}
|
||||
/* ckeditor5-basic-styles/theme/code.css */
|
||||
.ck-content code {
|
||||
background-color: hsla(0, 0%, 78%, 0.3);
|
||||
padding: .15em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* ckeditor5-block-quote/theme/blockquote.css */
|
||||
.ck-content blockquote {
|
||||
overflow: hidden;
|
||||
padding-right: 1.5em;
|
||||
padding-left: 1.5em;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
font-style: italic;
|
||||
border-left: solid 5px hsl(0, 0%, 80%);
|
||||
}
|
||||
/* ckeditor5-block-quote/theme/blockquote.css */
|
||||
.ck-content[dir="rtl"] blockquote {
|
||||
border-left: 0;
|
||||
border-right: solid 5px hsl(0, 0%, 80%);
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table {
|
||||
margin: 1em auto;
|
||||
display: table;
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px double hsl(0, 0%, 70%);
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table td,
|
||||
.ck-content .table table th {
|
||||
min-width: 2em;
|
||||
padding: .4em;
|
||||
border: 1px solid hsl(0, 0%, 75%);
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table th {
|
||||
font-weight: bold;
|
||||
background: hsla(0, 0%, 0%, 5%);
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content[dir="rtl"] .table th {
|
||||
text-align: right;
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content[dir="ltr"] .table th {
|
||||
text-align: left;
|
||||
}
|
||||
/* ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized img {
|
||||
width: 100%;
|
||||
}
|
||||
/* ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized > figcaption {
|
||||
display: block;
|
||||
}
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.ck-content .image {
|
||||
display: table;
|
||||
clear: both;
|
||||
text-align: center;
|
||||
margin: 1em auto;
|
||||
}
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.ck-content .image img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
min-width: 50px;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagecaption.css */
|
||||
.ck-content .image > figcaption {
|
||||
display: table-caption;
|
||||
@@ -266,17 +208,89 @@
|
||||
color: var(--ck-highlight-pen-green);
|
||||
background-color: transparent;
|
||||
}
|
||||
/* ckeditor5-horizontal-line/theme/horizontalline.css */
|
||||
.ck-content hr {
|
||||
border-width: 1px 0 0;
|
||||
border-style: solid;
|
||||
border-color: hsl(0, 0%, 37%);
|
||||
margin: 0;
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-side,
|
||||
.ck-content .image-style-align-left,
|
||||
.ck-content .image-style-align-center,
|
||||
.ck-content .image-style-align-right {
|
||||
max-width: 50%;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-side {
|
||||
float: right;
|
||||
margin-left: var(--ck-image-style-spacing);
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-left {
|
||||
float: left;
|
||||
margin-right: var(--ck-image-style-spacing);
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-right {
|
||||
float: right;
|
||||
margin-left: var(--ck-image-style-spacing);
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break {
|
||||
position: relative;
|
||||
clear: both;
|
||||
padding: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 2px dashed hsl(0, 0%, 77%);
|
||||
width: 100%;
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break__label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: .3em .6em;
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-radius: 2px;
|
||||
font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
|
||||
font-size: 0.75em;
|
||||
font-weight: bold;
|
||||
color: hsl(0, 0%, 20%);
|
||||
background: hsl(0, 0%, 100%);
|
||||
box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
/* ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-tiny {
|
||||
font-size: .7em;
|
||||
}
|
||||
/* ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-small {
|
||||
font-size: .85em;
|
||||
}
|
||||
/* ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-big {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
/* ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-huge {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
/* ckeditor5-code-block/theme/codeblock.css */
|
||||
.ck-content pre {
|
||||
padding: 1em;
|
||||
color: #353535;
|
||||
color: hsl(0, 0%, 20.8%);
|
||||
background: hsla(0, 0%, 78%, 0.3);
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-radius: 2px;
|
||||
@@ -293,6 +307,13 @@
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
/* ckeditor5-horizontal-line/theme/horizontalline.css */
|
||||
.ck-content hr {
|
||||
margin: 15px 0;
|
||||
height: 4px;
|
||||
background: hsl(0, 0%, 87%);
|
||||
border: 0;
|
||||
}
|
||||
@media print {
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break {
|
||||
@@ -303,3 +324,9 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* !!!!!! TRILIUM CUSTOM CHANGES !!!!!! */
|
||||
|
||||
.ck-widget__type-around { /* gets rid of triangles: https://github.com/zadam/trilium/issues/1129 */
|
||||
display: none;
|
||||
}
|
||||
|
||||
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
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);
|
||||
|
||||
293
package-lock.json
generated
293
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.41.5",
|
||||
"version": "0.43.0-beta",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -35,9 +35,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
|
||||
"integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
|
||||
"version": "6.12.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
|
||||
"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
@@ -1192,38 +1192,38 @@
|
||||
"integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="
|
||||
},
|
||||
"app-builder-bin": {
|
||||
"version": "3.5.6",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-3.5.6.tgz",
|
||||
"integrity": "sha512-gY9ABoV5jh67IrPEwF81R8l9LwE3RqHUyU3rIKitwqMpKhplN5OZC6WEHOXO3XhwiLCIlr9LLI6OPhr3bmtQIg==",
|
||||
"version": "3.5.8",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-3.5.8.tgz",
|
||||
"integrity": "sha512-ni3q7QTfQNWHNWuyn5x3FZu6GnQZv+TFnfgk5++svqleKEhHGqS1mIaKsh7x5pBX6NFXU3/+ktk98wA/AW4EXw==",
|
||||
"dev": true
|
||||
},
|
||||
"app-builder-lib": {
|
||||
"version": "22.5.1",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-22.5.1.tgz",
|
||||
"integrity": "sha512-VtB/PD8actR1317D/0uGzuJIYbpw4pRrfMB6IyTLwGynUd3ihqiCFjejVWHjCwopgCct2kE0MvLwo8P49xHIeQ==",
|
||||
"version": "22.6.0",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-22.6.0.tgz",
|
||||
"integrity": "sha512-ky2aLYy92U+Gh6dKq/e8/bNmCotp6/GMhnX8tDZPv9detLg9WuBnWWi1ktBPlpbl1DREusy+TIh+9rgvfduQoA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"7zip-bin": "~5.0.3",
|
||||
"@develar/schema-utils": "~2.6.5",
|
||||
"async-exit-hook": "^2.0.1",
|
||||
"bluebird-lst": "^1.0.9",
|
||||
"builder-util": "22.5.1",
|
||||
"builder-util": "22.6.0",
|
||||
"builder-util-runtime": "8.7.0",
|
||||
"chromium-pickle-js": "^0.2.0",
|
||||
"debug": "^4.1.1",
|
||||
"ejs": "^3.0.2",
|
||||
"electron-publish": "22.5.1",
|
||||
"ejs": "^3.1.2",
|
||||
"electron-publish": "22.6.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"hosted-git-info": "^3.0.4",
|
||||
"is-ci": "^2.0.0",
|
||||
"isbinaryfile": "^4.0.5",
|
||||
"isbinaryfile": "^4.0.6",
|
||||
"js-yaml": "^3.13.1",
|
||||
"lazy-val": "^1.0.4",
|
||||
"minimatch": "^3.0.4",
|
||||
"normalize-package-data": "^2.5.0",
|
||||
"read-config-file": "6.0.0",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"semver": "^7.1.3",
|
||||
"semver": "^7.3.2",
|
||||
"temp-file": "^3.3.7"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -1263,7 +1263,7 @@
|
||||
"dependencies": {
|
||||
"file-type": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
|
||||
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek="
|
||||
}
|
||||
}
|
||||
@@ -1539,7 +1539,7 @@
|
||||
},
|
||||
"uuid": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "http://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
|
||||
"integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho="
|
||||
}
|
||||
}
|
||||
@@ -1573,7 +1573,7 @@
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "http://registry.npmjs.org/semver/-/semver-4.3.6.tgz",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz",
|
||||
"integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto="
|
||||
}
|
||||
}
|
||||
@@ -1593,7 +1593,7 @@
|
||||
},
|
||||
"bl": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
|
||||
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
|
||||
"requires": {
|
||||
"readable-stream": "^2.3.5",
|
||||
@@ -1853,34 +1853,34 @@
|
||||
"dependencies": {
|
||||
"file-type": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
|
||||
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "http://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
|
||||
"integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho="
|
||||
}
|
||||
}
|
||||
},
|
||||
"builder-util": {
|
||||
"version": "22.5.1",
|
||||
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-22.5.1.tgz",
|
||||
"integrity": "sha512-CelDTP3+fvDfZfbwy3PXif7mudPaWankJ8vrRg/NtCGvL+hXnwycnJZr46d5EQL7AgQcpJ27o9LTdfu61cxTFw==",
|
||||
"version": "22.6.0",
|
||||
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-22.6.0.tgz",
|
||||
"integrity": "sha512-jgdES2ExJYkuXC3DEaGAjFctKNA81C4QDy8zdoc+rqdSqheTizuDNtZg02uMFklmUES4V4fggmqds+Y7wraqng==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"7zip-bin": "~5.0.3",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/fs-extra": "^8.1.0",
|
||||
"app-builder-bin": "3.5.6",
|
||||
"app-builder-bin": "3.5.8",
|
||||
"bluebird-lst": "^1.0.9",
|
||||
"builder-util-runtime": "8.7.0",
|
||||
"chalk": "^3.0.0",
|
||||
"chalk": "^4.0.0",
|
||||
"debug": "^4.1.1",
|
||||
"fs-extra": "^9.0.0",
|
||||
"is-ci": "^2.0.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"source-map-support": "^0.5.16",
|
||||
"source-map-support": "^0.5.19",
|
||||
"stat-mode": "^1.0.0",
|
||||
"temp-file": "^3.3.7"
|
||||
},
|
||||
@@ -1896,9 +1896,9 @@
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
||||
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
|
||||
"integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
@@ -1920,6 +1920,16 @@
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"stat-mode": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz",
|
||||
@@ -1963,7 +1973,7 @@
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
@@ -2138,7 +2148,7 @@
|
||||
},
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||
"requires": {
|
||||
"ansi-styles": "^2.2.1",
|
||||
@@ -2455,7 +2465,7 @@
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
|
||||
"integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
|
||||
"requires": {
|
||||
"graceful-readlink": ">= 1.0.0"
|
||||
@@ -2822,9 +2832,9 @@
|
||||
"integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI="
|
||||
},
|
||||
"dayjs": {
|
||||
"version": "1.8.25",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.25.tgz",
|
||||
"integrity": "sha512-Pk36juDfQQGDCgr0Lqd1kw15w3OS6xt21JaLPE3lCfsEf8KrERGwDNwvK1tRjrjqFC0uZBJncT4smZQ4F+uV5g=="
|
||||
"version": "1.8.26",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.26.tgz",
|
||||
"integrity": "sha512-KqtAuIfdNfZR5sJY1Dixr2Is4ZvcCqhb0dZpCOt5dGEFiMzoIbjkTSzUb4QKTCsP+WNpGwUjAFIZrnZvUxxkhw=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
@@ -3118,7 +3128,7 @@
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
@@ -3143,13 +3153,13 @@
|
||||
}
|
||||
},
|
||||
"dmg-builder": {
|
||||
"version": "22.5.1",
|
||||
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-22.5.1.tgz",
|
||||
"integrity": "sha512-AwIiyGwgqhA8Ty/YnEU20aSzfrWZns6suOBTqddD+rLDI4jEASKGQadfvcXRSWgaK/VQW0GrhheXrhJpzZzt3g==",
|
||||
"version": "22.6.0",
|
||||
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-22.6.0.tgz",
|
||||
"integrity": "sha512-rJxuGhHIpcuDGBtWZMM8aLxkbZNgYO2MO5dUerDIBXebhX1K8DA23iz/uZ8ahcRNgWEv57b8GDqJbXKEfr5T0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"app-builder-lib": "22.5.1",
|
||||
"builder-util": "22.5.1",
|
||||
"app-builder-lib": "22.6.0",
|
||||
"builder-util": "22.6.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"iconv-lite": "^0.5.1",
|
||||
"js-yaml": "^3.13.1",
|
||||
@@ -3335,9 +3345,9 @@
|
||||
}
|
||||
},
|
||||
"electron": {
|
||||
"version": "9.0.0-beta.20",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0-beta.20.tgz",
|
||||
"integrity": "sha512-B3JDQRGYi/hS/a64wDx1Xnl8Z52rpBbysh+D4KOKL95NJ+MLVifgCzhu9yHIFNblB5fqz7PFzbyllJ6wAoQMQg==",
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-9.0.5.tgz",
|
||||
"integrity": "sha512-bnL9H48LuQ250DML8xUscsKiuSu+xv5umXbpBXYJ0BfvYVmFfNbG3jCfhrsH7aP6UcQKVxOG1R/oQExd0EFneQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@electron/get": "^1.0.1",
|
||||
@@ -3346,18 +3356,18 @@
|
||||
}
|
||||
},
|
||||
"electron-builder": {
|
||||
"version": "22.5.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-22.5.1.tgz",
|
||||
"integrity": "sha512-7gnHN8Ml5zecDerN8/ljAwUKtE+hhGLuT/X2/zO0FJM2q2hlLx/6ZgzESFILKqnPQFEBRxQ8SL1OxjdIY0HIrw==",
|
||||
"version": "22.6.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-22.6.0.tgz",
|
||||
"integrity": "sha512-aLHlB6DTfjJ3MI4AUIFeWnwIozNgNlbOk2c2sTHxB10cAKp0dBVSPZ7xF5NK0uwDhElvRzJQubnHtJD6zKg42Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/yargs": "^15.0.4",
|
||||
"app-builder-lib": "22.5.1",
|
||||
"app-builder-lib": "22.6.0",
|
||||
"bluebird-lst": "^1.0.9",
|
||||
"builder-util": "22.5.1",
|
||||
"builder-util": "22.6.0",
|
||||
"builder-util-runtime": "8.7.0",
|
||||
"chalk": "^3.0.0",
|
||||
"dmg-builder": "22.5.1",
|
||||
"chalk": "^4.0.0",
|
||||
"dmg-builder": "22.6.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"is-ci": "^2.0.0",
|
||||
"lazy-val": "^1.0.4",
|
||||
@@ -3378,9 +3388,9 @@
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
||||
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
|
||||
"integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
@@ -3724,16 +3734,16 @@
|
||||
}
|
||||
},
|
||||
"electron-publish": {
|
||||
"version": "22.5.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.5.1.tgz",
|
||||
"integrity": "sha512-g5bwLAHZT6A++yU1+Et+fncnFAdXXgkRao9rzTFAvhQ0QJBsmLiyOd0Ta2RI/EQcVoy6jyHtxFs7CWIXE5aZOA==",
|
||||
"version": "22.6.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.6.0.tgz",
|
||||
"integrity": "sha512-+v05SBf9qR7Os5au+fifloNHy5QxHQkUGudBj68YaTb43Pn37UkwRxSc49Lf13s4wW32ohM45g8BOVInPJEdnA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/fs-extra": "^8.1.0",
|
||||
"bluebird-lst": "^1.0.9",
|
||||
"builder-util": "22.5.1",
|
||||
"builder-util": "22.6.0",
|
||||
"builder-util-runtime": "8.7.0",
|
||||
"chalk": "^3.0.0",
|
||||
"chalk": "^4.0.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"lazy-val": "^1.0.4",
|
||||
"mime": "^2.4.4"
|
||||
@@ -3750,9 +3760,9 @@
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
||||
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
|
||||
"integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
@@ -3775,9 +3785,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
|
||||
"integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==",
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.5.tgz",
|
||||
"integrity": "sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
@@ -4438,9 +4448,9 @@
|
||||
}
|
||||
},
|
||||
"file-type": {
|
||||
"version": "14.2.0",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-14.2.0.tgz",
|
||||
"integrity": "sha512-CAkX5G5jq8LIgFu++dpM3giMZadYdU+QVQoPLajjNboo8IzaR4cKpBCVEuz+suhd/vHqoAJeSWhEubKjRPQHJg==",
|
||||
"version": "14.3.0",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-14.3.0.tgz",
|
||||
"integrity": "sha512-s71v6jMkbfwVdj87csLeNpL5K93mv4lN+lzgzifoICtPHhnXokDwBa3jrzfg+z6FK872iYJ0vS0i74v8XmoFDA==",
|
||||
"requires": {
|
||||
"readable-web-to-node-stream": "^2.0.0",
|
||||
"strtok3": "^6.0.0",
|
||||
@@ -4947,7 +4957,7 @@
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
||||
},
|
||||
"getpass": {
|
||||
@@ -5211,7 +5221,7 @@
|
||||
},
|
||||
"got": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz",
|
||||
"integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=",
|
||||
"requires": {
|
||||
"create-error-class": "^3.0.1",
|
||||
@@ -5859,7 +5869,7 @@
|
||||
},
|
||||
"into-stream": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "http://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz",
|
||||
"integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=",
|
||||
"requires": {
|
||||
"from2": "^2.1.1",
|
||||
@@ -6011,7 +6021,7 @@
|
||||
},
|
||||
"is-obj": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
|
||||
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
|
||||
},
|
||||
"is-object": {
|
||||
@@ -6207,9 +6217,9 @@
|
||||
}
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "25.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.4.0.tgz",
|
||||
"integrity": "sha512-ghAs/1FtfYpMmYQ0AHqxV62XPvKdUDIBBApMZfly+E9JEmYh2K45G0R5dWxx986RN12pRCxsViwQVtGl+N4whw==",
|
||||
"version": "25.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz",
|
||||
"integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"merge-stream": "^2.0.0",
|
||||
@@ -6611,7 +6621,7 @@
|
||||
},
|
||||
"load-json-file": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
|
||||
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
@@ -7120,7 +7130,7 @@
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
||||
},
|
||||
"minipass": {
|
||||
@@ -7220,7 +7230,7 @@
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
@@ -7228,7 +7238,7 @@
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||
}
|
||||
}
|
||||
@@ -7422,7 +7432,7 @@
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
||||
},
|
||||
"got": {
|
||||
@@ -7458,7 +7468,7 @@
|
||||
},
|
||||
"p-cancelable": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "http://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
|
||||
"integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ=="
|
||||
},
|
||||
"p-event": {
|
||||
@@ -7582,7 +7592,7 @@
|
||||
"dependencies": {
|
||||
"file-type": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
|
||||
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek="
|
||||
}
|
||||
}
|
||||
@@ -7607,7 +7617,7 @@
|
||||
"dependencies": {
|
||||
"get-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
||||
},
|
||||
"pify": {
|
||||
@@ -7664,7 +7674,7 @@
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
|
||||
"integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
|
||||
"requires": {
|
||||
"object-assign": "^4.0.1",
|
||||
@@ -7694,7 +7704,7 @@
|
||||
"dependencies": {
|
||||
"get-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
||||
}
|
||||
}
|
||||
@@ -7734,7 +7744,7 @@
|
||||
},
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
|
||||
},
|
||||
"prepend-http": {
|
||||
@@ -7839,7 +7849,7 @@
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
@@ -7856,9 +7866,9 @@
|
||||
}
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.14.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
|
||||
"integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
|
||||
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "2.1.11",
|
||||
@@ -7867,9 +7877,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"needle": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz",
|
||||
"integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==",
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz",
|
||||
"integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==",
|
||||
"requires": {
|
||||
"debug": "^3.2.6",
|
||||
"iconv-lite": "^0.4.4",
|
||||
@@ -8088,9 +8098,12 @@
|
||||
}
|
||||
},
|
||||
"npm-bundled": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz",
|
||||
"integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g=="
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
|
||||
"integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
|
||||
"requires": {
|
||||
"npm-normalize-package-bin": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"npm-conf": {
|
||||
"version": "1.1.3",
|
||||
@@ -8108,13 +8121,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"npm-normalize-package-bin": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
|
||||
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
|
||||
},
|
||||
"npm-packlist": {
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.6.tgz",
|
||||
"integrity": "sha512-u65uQdb+qwtGvEJh/DgQgW1Xg7sqeNbmxYyrvlNznaVTjV3E5P6F/EFjM+BVHXl7JJlsdG8A64M0XI8FI/IOlg==",
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
|
||||
"integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
|
||||
"requires": {
|
||||
"ignore-walk": "^3.0.1",
|
||||
"npm-bundled": "^1.0.1"
|
||||
"npm-bundled": "^1.0.1",
|
||||
"npm-normalize-package-bin": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"npm-run-path": {
|
||||
@@ -8208,7 +8227,7 @@
|
||||
},
|
||||
"onetime": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
||||
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k="
|
||||
},
|
||||
"open": {
|
||||
@@ -8360,7 +8379,7 @@
|
||||
},
|
||||
"p-is-promise": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
|
||||
"integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4="
|
||||
},
|
||||
"p-limit": {
|
||||
@@ -8841,7 +8860,7 @@
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
||||
}
|
||||
}
|
||||
@@ -9125,7 +9144,7 @@
|
||||
"dependencies": {
|
||||
"file-type": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
|
||||
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek="
|
||||
}
|
||||
}
|
||||
@@ -9150,7 +9169,7 @@
|
||||
"dependencies": {
|
||||
"get-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
||||
},
|
||||
"pify": {
|
||||
@@ -9188,7 +9207,7 @@
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
||||
}
|
||||
}
|
||||
@@ -9240,7 +9259,7 @@
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
|
||||
"integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
|
||||
"requires": {
|
||||
"object-assign": "^4.0.1",
|
||||
@@ -9270,7 +9289,7 @@
|
||||
"dependencies": {
|
||||
"get-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
||||
}
|
||||
}
|
||||
@@ -9458,7 +9477,7 @@
|
||||
},
|
||||
"query-string": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "http://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
|
||||
"integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==",
|
||||
"requires": {
|
||||
"decode-uri-component": "^0.2.0",
|
||||
@@ -9597,7 +9616,7 @@
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
@@ -10098,9 +10117,9 @@
|
||||
}
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
|
||||
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.0.0.tgz",
|
||||
"integrity": "sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw==",
|
||||
"dev": true
|
||||
},
|
||||
"serve-favicon": {
|
||||
@@ -10253,9 +10272,9 @@
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
|
||||
"integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
@@ -10465,7 +10484,7 @@
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
@@ -10490,7 +10509,7 @@
|
||||
},
|
||||
"strip-dirs": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "http://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
|
||||
"integrity": "sha1-lgu9EoeETzl1pFWKoQOoJV4kVqA=",
|
||||
"requires": {
|
||||
"chalk": "^1.0.0",
|
||||
@@ -10690,9 +10709,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"terser": {
|
||||
"version": "4.6.11",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.6.11.tgz",
|
||||
"integrity": "sha512-76Ynm7OXUG5xhOpblhytE7X58oeNSmC8xnNhjWVo8CksHit0U0kO4hfNbPrrYwowLWFgM2n9L176VNx2QaHmtA==",
|
||||
"version": "4.6.13",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.6.13.tgz",
|
||||
"integrity": "sha512-wMvqukYgVpQlymbnNbabVZbtM6PN63AzqexpwJL8tbh/mRT9LE5o+ruVduAGL7D6Fpjl+Q+06U5I9Ul82odAhw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^2.20.0",
|
||||
@@ -10709,19 +10728,19 @@
|
||||
}
|
||||
},
|
||||
"terser-webpack-plugin": {
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.5.tgz",
|
||||
"integrity": "sha512-WlWksUoq+E4+JlJ+h+U+QUzXpcsMSSNXkDy9lBVkSqDn1w23Gg29L/ary9GeJVYCGiNJJX7LnVc4bwL1N3/g1w==",
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.6.tgz",
|
||||
"integrity": "sha512-I8IDsQwZrqjdmOicNeE8L/MhwatAap3mUrtcAKJuilsemUNcX+Hier/eAzwStVqhlCxq0aG3ni9bK/0BESXkTg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cacache": "^13.0.1",
|
||||
"find-cache-dir": "^3.2.0",
|
||||
"jest-worker": "^25.1.0",
|
||||
"p-limit": "^2.2.2",
|
||||
"schema-utils": "^2.6.4",
|
||||
"serialize-javascript": "^2.1.2",
|
||||
"find-cache-dir": "^3.3.1",
|
||||
"jest-worker": "^25.4.0",
|
||||
"p-limit": "^2.3.0",
|
||||
"schema-utils": "^2.6.6",
|
||||
"serialize-javascript": "^3.0.0",
|
||||
"source-map": "^0.6.1",
|
||||
"terser": "^4.4.3",
|
||||
"terser": "^4.6.12",
|
||||
"webpack-sources": "^1.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -10748,7 +10767,7 @@
|
||||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
|
||||
},
|
||||
"through2": {
|
||||
@@ -10767,7 +10786,7 @@
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.0.34",
|
||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
|
||||
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
@@ -11447,9 +11466,9 @@
|
||||
"integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA=="
|
||||
},
|
||||
"webpack": {
|
||||
"version": "5.0.0-beta.15",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.0.0-beta.15.tgz",
|
||||
"integrity": "sha512-nT+l7LteKTIzB3lmroEGL4qcCBqgHMpa3EJUvhQdfXRWjxCfWnnWdBARhp/To61omZhyNPz2Ye2J1ZEf070kWA==",
|
||||
"version": "5.0.0-beta.16",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.0.0-beta.16.tgz",
|
||||
"integrity": "sha512-O6YzI5H7XDPoXFrdC338P0GsSdhmYvz0//HL8LxVFHuRSbtHcV8mfx5U8ouWihFqwyvbfy27Bqoz2KY62kME9Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@webassemblyjs/ast": "1.9.0",
|
||||
@@ -11471,7 +11490,7 @@
|
||||
"pkg-dir": "^4.2.0",
|
||||
"schema-utils": "^2.5.0",
|
||||
"tapable": "2.0.0-beta.9",
|
||||
"terser-webpack-plugin": "^2.3.1",
|
||||
"terser-webpack-plugin": "^2.3.6",
|
||||
"watchpack": "2.0.0-beta.13",
|
||||
"webpack-sources": "2.0.0-beta.8"
|
||||
}
|
||||
|
||||
12
package.json
12
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.41.5",
|
||||
"version": "0.43.2",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@@ -28,7 +28,7 @@
|
||||
"commonmark": "0.29.1",
|
||||
"cookie-parser": "1.4.5",
|
||||
"csurf": "1.11.0",
|
||||
"dayjs": "1.8.25",
|
||||
"dayjs": "1.8.26",
|
||||
"debug": "4.1.1",
|
||||
"ejs": "3.1.2",
|
||||
"electron-debug": "3.0.1",
|
||||
@@ -37,7 +37,7 @@
|
||||
"electron-window-state": "5.0.3",
|
||||
"express": "4.17.1",
|
||||
"express-session": "1.17.1",
|
||||
"file-type": "14.2.0",
|
||||
"file-type": "14.3.0",
|
||||
"fs-extra": "9.0.0",
|
||||
"helmet": "3.22.0",
|
||||
"html": "1.0.0",
|
||||
@@ -78,13 +78,13 @@
|
||||
"yazl": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "9.0.0-beta.20",
|
||||
"electron-builder": "22.5.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.15",
|
||||
"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...");
|
||||
|
||||
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);
|
||||
});
|
||||
11
src/app.js
11
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());
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,7 @@ import contextMenu from "./services/context_menu.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();
|
||||
|
||||
@@ -85,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",
|
||||
@@ -133,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') {
|
||||
|
||||
@@ -17,7 +17,6 @@ export async function showDialog(widget) {
|
||||
|
||||
$addLinkTitleSettings.toggle(!textTypeWidget.hasSelection());
|
||||
|
||||
updateTitleFormGroupVisibility();
|
||||
$addLinkTitleSettings.find('input[type=radio]').on('change', updateTitleFormGroupVisibility);
|
||||
|
||||
// with selection hyper link is implied
|
||||
@@ -28,6 +27,8 @@ export async function showDialog(widget) {
|
||||
$addLinkTitleSettings.find("input[value='reference-link']").prop("checked", true);
|
||||
}
|
||||
|
||||
updateTitleFormGroupVisibility();
|
||||
|
||||
utils.openDialog($dialog);
|
||||
|
||||
$autoComplete.val('').trigger('focus');
|
||||
|
||||
@@ -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.");
|
||||
};
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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,9 +47,8 @@ $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.");
|
||||
|
||||
@@ -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">')
|
||||
$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('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,19 +60,30 @@ 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.");
|
||||
});
|
||||
|
||||
@@ -16,18 +16,18 @@ 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>');
|
||||
@@ -95,10 +95,10 @@ export async function showDialog(ancestorNoteId) {
|
||||
}
|
||||
}
|
||||
|
||||
function groupByDate(result) {
|
||||
function groupByDate(rows) {
|
||||
const groupedByDate = new Map();
|
||||
|
||||
for (const row of result) {
|
||||
for (const row of rows) {
|
||||
const dateDay = row.date.substr(0, 10);
|
||||
|
||||
if (!groupedByDate.has(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;
|
||||
@@ -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,17 +170,30 @@ class NoteShort {
|
||||
* @returns {Attribute[]} all note's attributes, including inherited ones
|
||||
*/
|
||||
getAttributes(type, name) {
|
||||
return this.__filterAttrs(this.__getCachedAttributes([]), type, name);
|
||||
}
|
||||
|
||||
__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 in noteAttributeCache)) {
|
||||
const ownedAttributes = this.getOwnedAttributes();
|
||||
|
||||
const attrArrs = [
|
||||
ownedAttributes
|
||||
];
|
||||
|
||||
for (const templateAttr of ownedAttributes.filter(oa => oa.type === 'relation' && oa.name === 'template')) {
|
||||
const templateNote = this.treeCache.getNoteFromCache(templateAttr.value);
|
||||
const newPath = [...path, this.noteId];
|
||||
|
||||
if (templateNote) {
|
||||
attrArrs.push(templateNote.getAttributes());
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,30 +201,31 @@ class NoteShort {
|
||||
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());
|
||||
attrArrs.push(parentNote.__getInheritableAttributes(newPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const attributes = attrArrs.flat();
|
||||
noteAttributeCache.attributes[this.noteId] = 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})`;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ 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 ProtectedNoteSwitchWidget from "../widgets/protected_note_switch.js";
|
||||
import NoteTypeWidget from "../widgets/note_type.js";
|
||||
import NoteActionsWidget from "../widgets/note_actions.js";
|
||||
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
||||
@@ -35,7 +34,6 @@ export default class DesktopExtraWindowLayout {
|
||||
.cssBlock('.title-row > * { margin: 5px; }')
|
||||
.child(new NoteTitleWidget())
|
||||
.child(new RunScriptButtonsWidget().hideInZenMode())
|
||||
.child(new ProtectedNoteSwitchWidget().hideInZenMode())
|
||||
.child(new NoteTypeWidget().hideInZenMode())
|
||||
.child(new NoteActionsWidget().hideInZenMode())
|
||||
)
|
||||
|
||||
@@ -12,7 +12,6 @@ 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 ProtectedNoteSwitchWidget from "../widgets/protected_note_switch.js";
|
||||
import NoteTypeWidget from "../widgets/note_type.js";
|
||||
import NoteActionsWidget from "../widgets/note_actions.js";
|
||||
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
||||
@@ -25,7 +24,7 @@ import NoteRevisionsWidget from "../widgets/collapsible_widgets/note_revisions.j
|
||||
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 appContext from "../services/app_context.js";
|
||||
import EditedNotesWidget from "../widgets/collapsible_widgets/edited_notes.js";
|
||||
|
||||
const RIGHT_PANE_CSS = `
|
||||
<style>
|
||||
@@ -104,7 +103,7 @@ export default class DesktopMainWindowLayout {
|
||||
}
|
||||
|
||||
getRootWidget(appContext) {
|
||||
appContext.mainTreeWidget = new NoteTreeWidget();
|
||||
appContext.mainTreeWidget = new NoteTreeWidget("main");
|
||||
|
||||
return new FlexContainer('column')
|
||||
.setParent(appContext)
|
||||
@@ -118,6 +117,7 @@ export default class DesktopMainWindowLayout {
|
||||
.hideInZenMode())
|
||||
.child(new FlexContainer('row')
|
||||
.collapsible()
|
||||
.filling()
|
||||
.child(new SidePaneContainer('left')
|
||||
.hideInZenMode()
|
||||
.child(new GlobalButtonsWidget())
|
||||
@@ -132,7 +132,6 @@ export default class DesktopMainWindowLayout {
|
||||
.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 DesktopMainWindowLayout {
|
||||
.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()))
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -4,7 +4,7 @@ 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";
|
||||
@@ -17,6 +17,7 @@ class AppContext extends Component {
|
||||
super();
|
||||
|
||||
this.isMainWindow = isMainWindow;
|
||||
this.executors = [];
|
||||
}
|
||||
|
||||
setLayout(layout) {
|
||||
@@ -45,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();
|
||||
@@ -71,7 +74,7 @@ class AppContext extends Component {
|
||||
}
|
||||
|
||||
if (utils.isElectron()) {
|
||||
this.child(new ZoomService());
|
||||
this.child(zoomService);
|
||||
}
|
||||
|
||||
this.triggerEvent('initialRenderComplete');
|
||||
@@ -92,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);
|
||||
@@ -100,19 +105,6 @@ class AppContext extends Component {
|
||||
getComponentByEl(el) {
|
||||
return $(el).closest(".component").prop('component');
|
||||
}
|
||||
|
||||
async openInNewWindow(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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const appContext = new AppContext(window.glob.isMainWindow);
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
if (e.which === 1) {
|
||||
const address = $link.attr('href');
|
||||
|
||||
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,8 +118,8 @@ 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 window", command: "openNoteInNewWindow", 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') {
|
||||
@@ -132,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);
|
||||
|
||||
@@ -155,18 +160,23 @@ $(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', ".note-paths-widget 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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -34,16 +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);
|
||||
|
||||
appContext.triggerCommand('focusAndSelectTitle');
|
||||
}
|
||||
|
||||
async createNoteAfterCommand() {
|
||||
@@ -55,17 +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);
|
||||
|
||||
appContext.triggerCommand('focusAndSelectTitle');
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
@@ -10,7 +10,7 @@ let protectedSessionDeferred = null;
|
||||
|
||||
async function leaveProtectedSession() {
|
||||
if (protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||
utils.reloadApp();
|
||||
protectedSessionHolder.resetProtectedSession();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,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);
|
||||
|
||||
@@ -42,7 +51,7 @@ async function setupProtectedSession(password) {
|
||||
protectedSessionHolder.setProtectedSessionId(response.protectedSessionId);
|
||||
protectedSessionHolder.touchProtectedSession();
|
||||
|
||||
await treeCache.loadInitialTree();
|
||||
await reloadData();
|
||||
|
||||
await appContext.triggerEvent('treeCacheReloaded');
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -73,7 +73,7 @@ class TabContext extends Component {
|
||||
protectedSessionHolder.touchProtectedSessionIfNecessary(this.note);
|
||||
|
||||
if (triggerSwitchEvent) {
|
||||
this.triggerEvent('tabNoteSwitched', {
|
||||
await this.triggerEvent('tabNoteSwitched', {
|
||||
tabContext: this,
|
||||
notePath: this.notePath
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ export default class TabManager extends Component {
|
||||
|
||||
if (filteredTabs.length === 0) {
|
||||
filteredTabs.push({
|
||||
notePath: 'root',
|
||||
notePath: this.isMainWindow ? 'root' : '',
|
||||
active: true
|
||||
});
|
||||
}
|
||||
@@ -196,12 +196,14 @@ export default class TabManager extends Component {
|
||||
async openTabWithNote(notePath, activate, tabId = null) {
|
||||
const tabContext = await this.openEmptyTab(tabId);
|
||||
|
||||
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
|
||||
});
|
||||
@@ -247,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) {
|
||||
@@ -327,7 +332,7 @@ export default class TabManager extends Component {
|
||||
|
||||
this.removeTab(tabId);
|
||||
|
||||
appContext.openInNewWindow(notePath);
|
||||
this.triggerCommand('openInWindow', {notePath});
|
||||
}
|
||||
|
||||
async hoistedNoteChangedEvent({hoistedNoteId}) {
|
||||
|
||||
@@ -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 {
|
||||
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 = '';
|
||||
|
||||
|
||||
@@ -1,181 +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
|
||||
};
|
||||
|
||||
node.folder = getChildBranchesWithoutImages(note).length > 0
|
||||
|| note.type === 'search';
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
async function prepareRealBranch(parentNote) {
|
||||
utils.assertArguments(parentNote);
|
||||
|
||||
const noteList = [];
|
||||
|
||||
for (const branch of getChildBranchesWithoutImages(parentNote)) {
|
||||
const node = await prepareNode(branch);
|
||||
|
||||
noteList.push(node);
|
||||
}
|
||||
|
||||
return noteList;
|
||||
}
|
||||
|
||||
function getChildBranchesWithoutImages(parentNote) {
|
||||
const childBranches = parentNote.getChildBranches();
|
||||
|
||||
if (!childBranches) {
|
||||
ws.logError(`No children for ${parentNote}. This shouldn't happen.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const imageLinks = parentNote.getRelations('imageLink');
|
||||
|
||||
// image is already visible in the parent note so no need to display it separately in the book
|
||||
return childBranches.filter(branch => !imageLinks.find(rel => rel.value === branch.noteId));
|
||||
}
|
||||
|
||||
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,
|
||||
getChildBranchesWithoutImages
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
@@ -57,7 +57,7 @@ class TreeContextMenu {
|
||||
|
||||
return [
|
||||
{ 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: "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 },
|
||||
@@ -74,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 }
|
||||
@@ -112,9 +113,6 @@ class TreeContextMenu {
|
||||
if (command === 'openInTab') {
|
||||
appContext.tabManager.openTabWithNote(notePath);
|
||||
}
|
||||
else if (command === 'openInWindow') {
|
||||
appContext.openInNewWindow(notePath);
|
||||
}
|
||||
else if (command === "insertNoteAfter") {
|
||||
const parentNoteId = this.node.data.parentNoteId;
|
||||
const isProtected = await treeService.getParentProtectedStatus(this.node);
|
||||
@@ -133,7 +131,7 @@ class TreeContextMenu {
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.treeWidget.triggerCommand(command, {node: this.node});
|
||||
this.treeWidget.triggerCommand(command, {node: this.node, notePath: notePath});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,19 @@ function assertArguments() {
|
||||
}
|
||||
}
|
||||
|
||||
const entityMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'/': '/',
|
||||
'`': '`',
|
||||
'=': '='
|
||||
};
|
||||
|
||||
function escapeHtml(str) {
|
||||
return $('<div/>').text(str).html();
|
||||
return str.replace(/[&<>"'`=\/]/g, s => entityMap[s]);
|
||||
}
|
||||
|
||||
async function stopWatch(what, func) {
|
||||
@@ -103,7 +114,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);
|
||||
}
|
||||
@@ -277,7 +288,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();
|
||||
}
|
||||
}
|
||||
@@ -316,6 +327,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,
|
||||
@@ -355,5 +384,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,7 +234,7 @@ subscribeToMessages(message => {
|
||||
async function processSyncRows(syncRows) {
|
||||
const missingNoteIds = [];
|
||||
|
||||
syncRows.forEach(({entityName, entity}) => {
|
||||
for (const {entityName, entity} of syncRows) {
|
||||
if (entityName === 'branches' && !(entity.parentNoteId in treeCache.notes)) {
|
||||
missingNoteIds.push(entity.parentNoteId);
|
||||
}
|
||||
@@ -222,7 +245,7 @@ async function processSyncRows(syncRows) {
|
||||
|
||||
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});
|
||||
}
|
||||
|
||||
@@ -5,11 +5,15 @@ 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();
|
||||
|
||||
if (utils.isElectron()) {
|
||||
options.initializedPromise.then(() => {
|
||||
this.setZoomFactor(options.getFloat('zoomFactor'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setZoomFactor(zoomFactor) {
|
||||
@@ -46,3 +50,7 @@ export default class ZoomService extends Component {
|
||||
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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -4,7 +4,6 @@ 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>
|
||||
<td>
|
||||
<span class="note-info-type"></span>
|
||||
|
||||
<th>MIME:</th>
|
||||
<td class="note-info-mime"></td>
|
||||
<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}) {
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
try {
|
||||
release = await this.mutex.acquire();
|
||||
|
||||
await fun.call(this, data);
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
if (release) {
|
||||
release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
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">
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,7 +186,9 @@ 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';
|
||||
}
|
||||
}
|
||||
@@ -195,7 +197,9 @@ export default class NoteDetailWidget extends TabAwareWidget {
|
||||
const noteComplement = await this.tabContext.getNoteComplement();
|
||||
|
||||
if (note.hasLabel('readOnly') ||
|
||||
(noteComplement.content && noteComplement.content.length > 30000)) {
|
||||
(noteComplement.content
|
||||
&& noteComplement.content.length > 30000)
|
||||
&& !note.hasLabel('autoReadOnlyDisabled')) {
|
||||
type = 'read-only-code';
|
||||
}
|
||||
}
|
||||
@@ -266,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() {
|
||||
@@ -304,7 +325,8 @@ 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
|
||||
});
|
||||
|
||||
@@ -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">
|
||||
@@ -52,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}) {
|
||||
@@ -80,6 +90,13 @@ 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();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import treeService from "../services/tree.js";
|
||||
import utils from "../services/utils.js";
|
||||
import contextMenu from "../services/context_menu.js";
|
||||
import treeCache from "../services/tree_cache.js";
|
||||
import treeBuilder from "../services/tree_builder.js";
|
||||
import branchService from "../services/branches.js";
|
||||
import ws from "../services/ws.js";
|
||||
import TabAwareWidget from "./tab_aware_widget.js";
|
||||
@@ -15,17 +14,24 @@ import keyboardActionsService from "../services/keyboard_actions.js";
|
||||
import clipboard from "../services/clipboard.js";
|
||||
import protectedSessionService from "../services/protected_session.js";
|
||||
import syncService from "../services/sync.js";
|
||||
import options from "../services/options.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="tree">
|
||||
<div class="tree-wrapper">
|
||||
<style>
|
||||
.tree {
|
||||
overflow: auto;
|
||||
.tree-wrapper {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
flex-basis: 60%;
|
||||
font-family: var(--tree-font-family);
|
||||
font-size: var(--tree-font-size);
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.tree {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.refresh-search-button {
|
||||
@@ -40,19 +46,147 @@ const TPL = `
|
||||
.refresh-search-button:hover {
|
||||
border-color: var(--button-border-color);
|
||||
}
|
||||
|
||||
.tree-settings-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.tree-settings-popup {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: var(--accented-background-color);
|
||||
border: 1px solid var(--main-border-color);
|
||||
padding: 20px;
|
||||
z-index: 1000;
|
||||
width: 320px;
|
||||
border-radius: 10px 0 10px 10px;
|
||||
}
|
||||
|
||||
ul.fancytree-container {
|
||||
outline: none !important;
|
||||
background-color: inherit !important;
|
||||
}
|
||||
|
||||
.fancytree-custom-icon {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
span.fancytree-title {
|
||||
color: inherit !important;
|
||||
background: inherit !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
span.fancytree-node.protected > span.fancytree-custom-icon {
|
||||
filter: drop-shadow(2px 2px 2px var(--main-text-color));
|
||||
}
|
||||
|
||||
span.fancytree-node.multiple-parents .fancytree-title::after {
|
||||
content: " *"
|
||||
}
|
||||
|
||||
span.fancytree-node.fancytree-active-clone:not(.fancytree-active) .fancytree-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* first nesting level has lower left padding to avoid extra left padding. Other levels are not affected */
|
||||
.ui-fancytree > li > ul {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
span.fancytree-active .fancytree-title {
|
||||
font-weight: bold;
|
||||
border-color: var(--main-border-color) !important;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
span.fancytree-active:not(.fancytree-focused) .fancytree-title {
|
||||
border-style: dashed !important;
|
||||
}
|
||||
|
||||
span.fancytree-focused .fancytree-title, span.fancytree-focused.fancytree-selected .fancytree-title {
|
||||
color: var(--active-item-text-color) !important;
|
||||
background-color: var(--active-item-background-color) !important;
|
||||
border-color: var(--main-background-color) !important; /* invisible border */
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
span.fancytree-selected .fancytree-title {
|
||||
color: var(--hover-item-text-color) !important;
|
||||
background-color: var(--hover-item-background-color) !important;
|
||||
border-color: var(--main-background-color) !important; /* invisible border */
|
||||
border-radius: 5px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
span.fancytree-node:hover span.fancytree-title {
|
||||
border-color: var(--main-border-color) !important;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
span.fancytree-node.archived {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button class="btn btn-sm icon-button bx bx-cog tree-settings-button" title="Tree settings"></button>
|
||||
|
||||
<div class="tree-settings-popup">
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input hide-archived-notes" type="checkbox" value="">
|
||||
|
||||
Hide archived notes
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input hide-included-images" type="checkbox" value="">
|
||||
|
||||
Hide images included in a note
|
||||
<span class="bx bx-info-circle"
|
||||
title="Images which are shown in the parent text note will not be displayed in the tree"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<button class="btn btn-sm btn-primary save-tree-settings-button" type="submit">Save & apply changes</button>
|
||||
</div>
|
||||
|
||||
<div class="tree"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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"
|
||||
};
|
||||
|
||||
export default class NoteTreeWidget extends TabAwareWidget {
|
||||
constructor(treeName) {
|
||||
super();
|
||||
|
||||
this.treeName = treeName;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$tree = this.$widget.find('.tree');
|
||||
|
||||
this.$widget.on("click", ".unhoist-button", hoistedNoteService.unhoist);
|
||||
this.$widget.on("click", ".refresh-search-button", () => this.refreshSearch());
|
||||
this.$tree.on("click", ".unhoist-button", hoistedNoteService.unhoist);
|
||||
this.$tree.on("click", ".refresh-search-button", () => this.refreshSearch());
|
||||
|
||||
// fancytree doesn't support middle click so this is a way to support it
|
||||
this.$widget.on('mousedown', '.fancytree-title', e => {
|
||||
this.$tree.on('mousedown', '.fancytree-title', e => {
|
||||
if (e.which === 2) {
|
||||
const node = $.ui.fancytree.getNode(e);
|
||||
|
||||
@@ -67,20 +201,113 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
});
|
||||
|
||||
this.$treeSettingsPopup = this.$widget.find('.tree-settings-popup');
|
||||
this.$hideArchivedNotesCheckbox = this.$treeSettingsPopup.find('.hide-archived-notes');
|
||||
this.$hideIncludedImages = this.$treeSettingsPopup.find('.hide-included-images');
|
||||
|
||||
this.$treeSettingsButton = this.$widget.find('.tree-settings-button');
|
||||
this.$treeSettingsButton.on("click", e => {
|
||||
if (this.$treeSettingsPopup.is(":visible")) {
|
||||
this.$treeSettingsPopup.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this.$hideArchivedNotesCheckbox.prop("checked", this.hideArchivedNotes);
|
||||
this.$hideIncludedImages.prop("checked", this.hideIncludedImages);
|
||||
|
||||
let top = this.$treeSettingsButton[0].offsetTop;
|
||||
let left = this.$treeSettingsButton[0].offsetLeft;
|
||||
top += this.$treeSettingsButton.outerHeight();
|
||||
left += this.$treeSettingsButton.outerWidth() - this.$treeSettingsPopup.outerWidth();
|
||||
|
||||
if (left < 0) {
|
||||
left = 0;
|
||||
}
|
||||
|
||||
this.$treeSettingsPopup.css({
|
||||
display: "block",
|
||||
top: top,
|
||||
left: left
|
||||
}).addClass("show");
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$treeSettingsPopup.on("click", e => { e.stopPropagation(); });
|
||||
|
||||
$(document).on('click', () => this.$treeSettingsPopup.hide());
|
||||
|
||||
this.$saveTreeSettingsButton = this.$treeSettingsPopup.find('.save-tree-settings-button');
|
||||
this.$saveTreeSettingsButton.on('click', async () => {
|
||||
await this.setHideArchivedNotes(this.$hideArchivedNotesCheckbox.prop("checked"));
|
||||
await this.setHideIncludedImages(this.$hideIncludedImages.prop("checked"));
|
||||
|
||||
this.$treeSettingsPopup.hide();
|
||||
|
||||
this.reloadTreeFromCache();
|
||||
});
|
||||
|
||||
this.initialized = this.initFancyTree();
|
||||
|
||||
this.setupNoteTitleTooltip();
|
||||
|
||||
return this.$widget;
|
||||
}
|
||||
|
||||
async initFancyTree() {
|
||||
const treeData = [await treeBuilder.prepareRootNode()];
|
||||
setupNoteTitleTooltip() {
|
||||
// the following will dynamically set tree item's tooltip if the whole item's text is not currently visible
|
||||
// if the whole text is visible then no tooltip is show since that's unnecessarily distracting
|
||||
// see https://github.com/zadam/trilium/pull/1120 for discussion
|
||||
|
||||
this.$widget.fancytree({
|
||||
// code inspired by https://gist.github.com/jtsternberg/c272d7de5b967cec2d3d
|
||||
const isEnclosing = ($container, $sub) => {
|
||||
const conOffset = $container.offset();
|
||||
const conDistanceFromTop = conOffset.top + $container.outerHeight(true);
|
||||
const conDistanceFromLeft = conOffset.left + $container.outerWidth(true);
|
||||
|
||||
const subOffset = $sub.offset();
|
||||
const subDistanceFromTop = subOffset.top + $sub.outerHeight(true);
|
||||
const subDistanceFromLeft = subOffset.left + $sub.outerWidth(true);
|
||||
|
||||
return conDistanceFromTop > subDistanceFromTop
|
||||
&& conOffset.top < subOffset.top
|
||||
&& conDistanceFromLeft > subDistanceFromLeft
|
||||
&& conOffset.left < subOffset.left;
|
||||
};
|
||||
|
||||
this.$tree.on("mouseenter", "span.fancytree-title", e => {
|
||||
e.currentTarget.title = isEnclosing(this.$tree, $(e.currentTarget))
|
||||
? ""
|
||||
: e.currentTarget.innerText;
|
||||
});
|
||||
}
|
||||
|
||||
get hideArchivedNotes() {
|
||||
return options.is("hideArchivedNotes_" + this.treeName);
|
||||
}
|
||||
|
||||
async setHideArchivedNotes(val) {
|
||||
await options.save("hideArchivedNotes_" + this.treeName, val.toString());
|
||||
}
|
||||
|
||||
get hideIncludedImages() {
|
||||
return options.is("hideIncludedImages_" + this.treeName);
|
||||
}
|
||||
|
||||
async setHideIncludedImages(val) {
|
||||
await options.save("hideIncludedImages_" + this.treeName, val.toString());
|
||||
}
|
||||
|
||||
async initFancyTree() {
|
||||
const treeData = [await this.prepareRootNode()];
|
||||
|
||||
this.$tree.fancytree({
|
||||
titlesTabbable: true,
|
||||
autoScroll: true,
|
||||
keyboard: false, // we takover keyboard handling in the hotkeys plugin
|
||||
extensions: utils.isMobile() ? ["dnd5", "clones"] : ["hotkeys", "dnd5", "clones"],
|
||||
source: treeData,
|
||||
scrollParent: this.$widget,
|
||||
scrollParent: this.$tree,
|
||||
minExpandLevel: 2, // root can't be collapsed
|
||||
click: (event, data) => {
|
||||
const targetType = data.targetType;
|
||||
@@ -121,8 +348,8 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
this.triggerCommand('setActiveScreen', {screen:'detail'});
|
||||
}
|
||||
},
|
||||
expand: (event, data) => this.setExpandedToServer(data.node.data.branchId, true),
|
||||
collapse: (event, data) => this.setExpandedToServer(data.node.data.branchId, false),
|
||||
expand: (event, data) => this.setExpanded(data.node.data.branchId, true),
|
||||
collapse: (event, data) => this.setExpanded(data.node.data.branchId, false),
|
||||
hotkeys: utils.isMobile() ? undefined : { keydown: await this.getHotKeys() },
|
||||
dnd5: {
|
||||
autoExpandMS: 600,
|
||||
@@ -135,6 +362,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
|
||||
const notes = this.getSelectedOrActiveNodes(node).map(node => ({
|
||||
noteId: node.data.noteId,
|
||||
branchId: node.data.branchId,
|
||||
title: node.title
|
||||
}));
|
||||
|
||||
@@ -174,27 +402,48 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
});
|
||||
}
|
||||
else {
|
||||
const jsonStr = dataTransfer.getData("text");
|
||||
let notes = null;
|
||||
|
||||
try {
|
||||
notes = JSON.parse(jsonStr);
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`Cannot parse ${jsonStr} into notes for drop`);
|
||||
return;
|
||||
}
|
||||
|
||||
// This function MUST be defined to enable dropping of items on the tree.
|
||||
// data.hitMode is 'before', 'after', or 'over'.
|
||||
|
||||
const selectedBranchIds = this.getSelectedNodes().map(node => node.data.branchId);
|
||||
const selectedBranchIds = notes.map(note => note.branchId);
|
||||
|
||||
if (data.hitMode === "before") {
|
||||
branchService.moveBeforeBranch(selectedBranchIds, node.data.branchId);
|
||||
} else if (data.hitMode === "after") {
|
||||
branchService.moveAfterBranch(selectedBranchIds, node.data.branchId);
|
||||
} else if (data.hitMode === "over") {
|
||||
branchService.moveToParentNote(selectedBranchIds, node.data.noteId);
|
||||
branchService.moveToParentNote(selectedBranchIds, node.data.branchId);
|
||||
} else {
|
||||
throw new Error("Unknown hitMode=" + data.hitMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
lazyLoad: function(event, data) {
|
||||
const noteId = data.node.data.noteId;
|
||||
lazyLoad: (event, data) => {
|
||||
const {noteId, noteType} = data.node.data;
|
||||
|
||||
data.result = treeCache.getNote(noteId).then(note => treeBuilder.prepareBranch(note));
|
||||
if (noteType === 'search') {
|
||||
const notePath = treeService.getNotePath(data.node.getParent());
|
||||
|
||||
// this is a search cycle (search note is a descendant of its own search result)
|
||||
if (notePath.includes(noteId)) {
|
||||
data.result = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
data.result = treeCache.getNote(noteId).then(note => this.prepareChildren(note));
|
||||
},
|
||||
clones: {
|
||||
highlightActiveClones: true
|
||||
@@ -220,7 +469,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
$span.append(refreshSearchButton);
|
||||
}
|
||||
},
|
||||
// this is done to automatically lazy load all expanded search notes after tree load
|
||||
// this is done to automatically lazy load all expanded notes after tree load
|
||||
loadChildren: (event, data) => {
|
||||
data.node.visit((subNode) => {
|
||||
// Load all lazy/unloaded child nodes
|
||||
@@ -232,7 +481,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
});
|
||||
|
||||
this.$widget.on('contextmenu', '.fancytree-node', e => {
|
||||
this.$tree.on('contextmenu', '.fancytree-node', e => {
|
||||
const node = $.ui.fancytree.getNode(e);
|
||||
|
||||
import("../services/tree_context_menu.js").then(({default: TreeContextMenu}) => {
|
||||
@@ -243,7 +492,200 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
return false; // blocks default browser right click menu
|
||||
});
|
||||
|
||||
this.tree = $.ui.fancytree.getTree(this.$widget);
|
||||
this.tree = $.ui.fancytree.getTree(this.$tree);
|
||||
}
|
||||
|
||||
async 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 this.prepareNode(hoistedBranch);
|
||||
}
|
||||
|
||||
async prepareChildren(note) {
|
||||
if (note.type === 'search') {
|
||||
return await this.prepareSearchNoteChildren(note);
|
||||
}
|
||||
else {
|
||||
return await this.prepareNormalNoteChildren(note);
|
||||
}
|
||||
}
|
||||
|
||||
getIconClass(note) {
|
||||
const labels = note.getLabels('iconClass');
|
||||
|
||||
return labels.map(l => l.value).join(' ');
|
||||
}
|
||||
|
||||
getIcon(note, isFolder) {
|
||||
const hoistedNoteId = hoistedNoteService.getHoistedNoteId();
|
||||
|
||||
const iconClass = this.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 (isFolder) {
|
||||
return "bx bx-folder";
|
||||
}
|
||||
else {
|
||||
return "bx bx-note";
|
||||
}
|
||||
}
|
||||
else {
|
||||
return NOTE_TYPE_ICONS[note.type];
|
||||
}
|
||||
}
|
||||
|
||||
async 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 isFolder = this.isFolder(note);
|
||||
|
||||
const node = {
|
||||
noteId: note.noteId,
|
||||
parentNoteId: branch.parentNoteId,
|
||||
branchId: branch.branchId,
|
||||
isProtected: note.isProtected,
|
||||
noteType: note.type,
|
||||
title: utils.escapeHtml(title),
|
||||
extraClasses: this.getExtraClasses(note),
|
||||
icon: this.getIcon(note, isFolder),
|
||||
refKey: note.noteId,
|
||||
lazy: true,
|
||||
folder: isFolder,
|
||||
expanded: branch.isExpanded || hoistedNoteId === note.noteId,
|
||||
key: utils.randomString(12) // this should prevent some "duplicate key" errors
|
||||
};
|
||||
|
||||
if (node.folder && node.expanded) {
|
||||
node.children = await this.prepareChildren(note);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
isFolder(note) {
|
||||
if (note.type === 'search') {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
const childBranches = this.getChildBranches(note);
|
||||
|
||||
return childBranches.length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
async prepareNormalNoteChildren(parentNote) {
|
||||
utils.assertArguments(parentNote);
|
||||
|
||||
const noteList = [];
|
||||
|
||||
const hideArchivedNotes = this.hideArchivedNotes;
|
||||
|
||||
for (const branch of this.getChildBranches(parentNote)) {
|
||||
if (hideArchivedNotes) {
|
||||
const note = await branch.getNote();
|
||||
|
||||
if (note.hasLabel('archived')) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const node = await this.prepareNode(branch);
|
||||
|
||||
noteList.push(node);
|
||||
}
|
||||
|
||||
return noteList;
|
||||
}
|
||||
|
||||
getChildBranches(parentNote) {
|
||||
let childBranches = parentNote.getChildBranches();
|
||||
|
||||
if (!childBranches) {
|
||||
ws.logError(`No children for ${parentNote}. This shouldn't happen.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hideIncludedImages) {
|
||||
const imageLinks = parentNote.getRelations('imageLink');
|
||||
|
||||
// image is already visible in the parent note so no need to display it separately in the book
|
||||
childBranches = childBranches.filter(branch => !imageLinks.find(rel => rel.value === branch.noteId));
|
||||
}
|
||||
|
||||
// we're not checking hideArchivedNotes since that would mean we need to lazy load the child notes
|
||||
// which would seriously slow down everything.
|
||||
// we check this flag only once user chooses to expand the parent. This has the negative consequence that
|
||||
// note may appear as folder but not contain any children when all of them are archived
|
||||
|
||||
return childBranches;
|
||||
}
|
||||
|
||||
async prepareSearchNoteChildren(note) {
|
||||
await treeCache.reloadNotes([note.noteId]);
|
||||
|
||||
const newNote = await treeCache.getNote(note.noteId);
|
||||
|
||||
return await this.prepareNormalNoteChildren(newNote);
|
||||
}
|
||||
|
||||
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(" ");
|
||||
}
|
||||
|
||||
/** @return {FancytreeNode[]} */
|
||||
@@ -253,25 +695,46 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
|
||||
/** @return {FancytreeNode[]} */
|
||||
getSelectedOrActiveNodes(node = null) {
|
||||
const notes = this.getSelectedNodes(true);
|
||||
const nodes = this.getSelectedNodes(true);
|
||||
|
||||
if (notes.length === 0) {
|
||||
notes.push(node ? node : this.getActiveNode());
|
||||
// the node you start dragging should be included even if not selected
|
||||
if (node && !nodes.find(n => n.key === node.key)) {
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
return notes;
|
||||
if (nodes.length === 0) {
|
||||
nodes.push(this.getActiveNode());
|
||||
}
|
||||
|
||||
collapseTree(node = null) {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
async setExpandedStatusForSubtree(node, isExpanded) {
|
||||
if (!node) {
|
||||
const hoistedNoteId = hoistedNoteService.getHoistedNoteId();
|
||||
|
||||
node = this.getNodesByNoteId(hoistedNoteId)[0];
|
||||
}
|
||||
|
||||
node.setExpanded(false);
|
||||
const {branchIds} = await server.put(`branches/${node.data.branchId}/expanded-subtree/${isExpanded ? 1 : 0}`);
|
||||
|
||||
node.visit(node => node.setExpanded(false));
|
||||
treeCache.getBranches(branchIds, true).forEach(branch => branch.isExpanded = isExpanded);
|
||||
|
||||
await this.batchUpdate(async () => {
|
||||
await node.load(true);
|
||||
|
||||
if (node.data.noteId !== 'root') { // root is always expanded
|
||||
await node.setExpanded(isExpanded, {noEvents: true});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async expandTree(node = null) {
|
||||
await this.setExpandedStatusForSubtree(node, true);
|
||||
}
|
||||
|
||||
async collapseTree(node = null) {
|
||||
await this.setExpandedStatusForSubtree(node, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,27 +763,30 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
const activeContext = appContext.tabManager.getActiveTabContext();
|
||||
|
||||
if (activeContext && activeContext.notePath) {
|
||||
this.tree.setFocus();
|
||||
this.tree.setFocus(true);
|
||||
|
||||
const node = await this.expandToNote(activeContext.notePath);
|
||||
|
||||
await node.makeVisible({scrollIntoView: true});
|
||||
node.setFocus();
|
||||
node.setFocus(true);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return {FancytreeNode} */
|
||||
async getNodeFromPath(notePath, expand = false, expandOpts = {}) {
|
||||
async getNodeFromPath(notePath, expand = false, logErrors = true) {
|
||||
utils.assertArguments(notePath);
|
||||
|
||||
const hoistedNoteId = hoistedNoteService.getHoistedNoteId();
|
||||
/** @var {FancytreeNode} */
|
||||
/** @const {FancytreeNode} */
|
||||
let parentNode = null;
|
||||
|
||||
const runPath = await treeService.getRunPath(notePath);
|
||||
const runPath = await treeService.getRunPath(notePath, logErrors);
|
||||
|
||||
if (!runPath) {
|
||||
if (logErrors) {
|
||||
console.error("Could not find run path for notePath:", notePath);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -339,7 +805,12 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
|
||||
if (expand) {
|
||||
await parentNode.setExpanded(true, expandOpts);
|
||||
await parentNode.setExpanded(true);
|
||||
|
||||
// although previous line should set the expanded status, it seems to happen asynchronously
|
||||
// so we need to make sure it is set properly before calling updateNode which uses this flag
|
||||
const branch = treeCache.getBranch(parentNode.data.branchId);
|
||||
branch.isExpanded = true;
|
||||
}
|
||||
|
||||
this.updateNode(parentNode);
|
||||
@@ -352,7 +823,10 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
foundChildNode = this.findChildNode(parentNode, childNoteId);
|
||||
|
||||
if (!foundChildNode) {
|
||||
if (logErrors) {
|
||||
ws.logError(`Can't find node for child node of noteId=${childNoteId} for parent of noteId=${parentNode.data.noteId} and hoistedNoteId=${hoistedNoteId}, requested path is ${notePath}`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -379,21 +853,28 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
|
||||
/** @return {FancytreeNode} */
|
||||
async expandToNote(notePath, expandOpts) {
|
||||
return this.getNodeFromPath(notePath, true, expandOpts);
|
||||
async expandToNote(notePath, logErrors = true) {
|
||||
return this.getNodeFromPath(notePath, true, logErrors);
|
||||
}
|
||||
|
||||
updateNode(node) {
|
||||
const note = treeCache.getNoteFromCache(node.data.noteId);
|
||||
const branch = treeCache.getBranch(node.data.branchId);
|
||||
|
||||
const isFolder = this.isFolder(note);
|
||||
const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title;
|
||||
|
||||
node.data.isProtected = note.isProtected;
|
||||
node.data.noteType = note.type;
|
||||
node.folder = treeBuilder.getChildBranchesWithoutImages(note).length > 0
|
||||
|| note.type === 'search';
|
||||
node.icon = treeBuilder.getIcon(note);
|
||||
node.extraClasses = treeBuilder.getExtraClasses(note);
|
||||
node.title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title;
|
||||
node.folder = isFolder;
|
||||
node.icon = this.getIcon(note, isFolder);
|
||||
node.extraClasses = this.getExtraClasses(note);
|
||||
node.title = utils.escapeHtml(title);
|
||||
|
||||
if (node.isExpanded() !== branch.isExpanded) {
|
||||
node.setExpanded(branch.isExpanded, {noEvents: true});
|
||||
}
|
||||
|
||||
node.renderTitle();
|
||||
}
|
||||
|
||||
@@ -414,12 +895,6 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
return list ? list : []; // if no nodes with this refKey are found, fancy tree returns null
|
||||
}
|
||||
|
||||
async reload() {
|
||||
const rootNode = await treeBuilder.prepareRootNode();
|
||||
|
||||
await this.tree.reload([rootNode]);
|
||||
}
|
||||
|
||||
// must be event since it's triggered from outside the tree
|
||||
collapseTreeEvent() { this.collapseTree(); }
|
||||
|
||||
@@ -431,8 +906,11 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
this.toggleInt(this.isEnabled());
|
||||
|
||||
const oldActiveNode = this.getActiveNode();
|
||||
let oldActiveNodeFocused = false;
|
||||
|
||||
if (oldActiveNode) {
|
||||
oldActiveNodeFocused = oldActiveNode.hasFocus();
|
||||
|
||||
oldActiveNode.setActive(false);
|
||||
oldActiveNode.setFocus(false);
|
||||
}
|
||||
@@ -445,8 +923,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
await this.expandToNote(this.tabContext.notePath);
|
||||
}
|
||||
|
||||
newActiveNode.setActive(true, {noEvents: true});
|
||||
|
||||
newActiveNode.setActive(true, {noEvents: true, noFocus: !oldActiveNodeFocused});
|
||||
newActiveNode.makeVisible({scrollIntoView: true});
|
||||
}
|
||||
}
|
||||
@@ -461,9 +938,24 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
toastService.showMessage("Saved search note refreshed.");
|
||||
}
|
||||
|
||||
async batchUpdate(cb) {
|
||||
try {
|
||||
// disable rendering during update for increased performance
|
||||
this.tree.enableUpdate(false);
|
||||
|
||||
await cb();
|
||||
}
|
||||
finally {
|
||||
this.tree.enableUpdate(true);
|
||||
}
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({loadResults}) {
|
||||
const activeNode = this.getActiveNode();
|
||||
const activeNodeFocused = activeNode && activeNode.hasFocus();
|
||||
const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null;
|
||||
const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
|
||||
const nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null;
|
||||
const activeNoteId = activeNode ? activeNode.data.noteId : null;
|
||||
|
||||
const noteIdsToUpdate = new Set();
|
||||
@@ -506,7 +998,9 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
}
|
||||
|
||||
if (node.getParent()) {
|
||||
node.remove();
|
||||
}
|
||||
|
||||
noteIdsToUpdate.add(branch.parentNoteId);
|
||||
}
|
||||
@@ -534,17 +1028,12 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
noteIdsToUpdate.add(noteId);
|
||||
}
|
||||
|
||||
await this.batchUpdate(async () => {
|
||||
for (const noteId of noteIdsToReload) {
|
||||
for (const node of this.getNodesByNoteId(noteId)) {
|
||||
await node.load(true);
|
||||
|
||||
this.updateNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
for (const noteId of noteIdsToUpdate) {
|
||||
for (const node of this.getNodesByNoteId(noteId)) {
|
||||
this.updateNode(node);
|
||||
noteIdsToUpdate.add(noteId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,9 +1053,17 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// for some reason node update cannot be in the batchUpdate() block (node is not re-rendered)
|
||||
for (const noteId of noteIdsToUpdate) {
|
||||
for (const node of this.getNodesByNoteId(noteId)) {
|
||||
this.updateNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (activeNotePath) {
|
||||
let node = await this.expandToNote(activeNotePath);
|
||||
let node = await this.expandToNote(activeNotePath, false);
|
||||
|
||||
if (node && node.data.noteId !== activeNoteId) {
|
||||
// if the active note has been moved elsewhere then it won't be found by the path
|
||||
@@ -578,17 +1075,33 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
|
||||
if (node) {
|
||||
node.setActive(true, {noEvents: true});
|
||||
node.setActive(true, {noEvents: true, noFocus: true});
|
||||
}
|
||||
else {
|
||||
// this is used when original note has been deleted and we want to move the focus to the note above/below
|
||||
node = await this.expandToNote(nextNotePath, false);
|
||||
|
||||
if (node) {
|
||||
await appContext.tabManager.getActiveTabContext().setNote(nextNotePath);
|
||||
}
|
||||
}
|
||||
|
||||
const newActiveNode = this.getActiveNode();
|
||||
|
||||
// return focus if the previously active node was also focused
|
||||
if (newActiveNode && activeNodeFocused) {
|
||||
await newActiveNode.setFocus(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async setExpandedToServer(branchId, isExpanded) {
|
||||
async setExpanded(branchId, isExpanded) {
|
||||
utils.assertArguments(branchId);
|
||||
|
||||
const expandedNum = isExpanded ? 1 : 0;
|
||||
const branch = treeCache.getBranch(branchId);
|
||||
branch.isExpanded = isExpanded;
|
||||
|
||||
await server.put('branches/' + branchId + '/expanded/' + expandedNum);
|
||||
await server.put(`branches/${branchId}/expanded/${isExpanded ? 1 : 0}`);
|
||||
}
|
||||
|
||||
async reloadTreeFromCache() {
|
||||
@@ -596,12 +1109,16 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
|
||||
const activeNotePath = activeNode !== null ? treeService.getNotePath(activeNode) : null;
|
||||
|
||||
await this.reload();
|
||||
const rootNode = await this.prepareRootNode();
|
||||
|
||||
await this.batchUpdate(async () => {
|
||||
await this.tree.reload([rootNode]);
|
||||
});
|
||||
|
||||
if (activeNotePath) {
|
||||
const node = await this.getNodeFromPath(activeNotePath, true);
|
||||
|
||||
await node.setActive(true, {noEvents: true});
|
||||
await node.setActive(true, {noEvents: true, noFocus: true});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -698,7 +1215,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
const toNode = node.getPrevSibling();
|
||||
|
||||
if (toNode !== null) {
|
||||
branchService.moveToParentNote([node.data.branchId], toNode.data.noteId);
|
||||
branchService.moveToParentNote([node.data.branchId], toNode.data.branchId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -750,6 +1267,10 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
}
|
||||
|
||||
expandSubtreeCommand({node}) {
|
||||
this.expandTree(node);
|
||||
}
|
||||
|
||||
collapseSubtreeCommand({node}) {
|
||||
this.collapseTree(node);
|
||||
}
|
||||
@@ -779,7 +1300,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
|
||||
pasteNotesFromClipboardCommand({node}) {
|
||||
clipboard.pasteInto(node.data.noteId);
|
||||
clipboard.pasteInto(node.data.branchId);
|
||||
}
|
||||
|
||||
pasteNotesAfterFromClipboard({node}) {
|
||||
|
||||
@@ -19,6 +19,7 @@ const TPL = `
|
||||
|
||||
.promoted-attributes td, .promoted-attributes th {
|
||||
padding: 5px;
|
||||
min-width: 50px; /* otherwise checkboxes can collapse into 0 width (if there are only checkboxes) */
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -98,7 +99,7 @@ export default class PromotedAttributesWidget extends TabAwareWidget {
|
||||
const $labelCell = $("<th>").append(valueAttr.name);
|
||||
const $input = $("<input>")
|
||||
.prop("tabindex", definitionAttr.position)
|
||||
.prop("attribute-id", valueAttr.isOwned ? valueAttr.attributeId : '') // if not owned, we'll force creation of a new attribute instead of updating the inherited one
|
||||
.prop("attribute-id", valueAttr.noteId === this.noteId ? valueAttr.attributeId : '') // if not owned, we'll force creation of a new attribute instead of updating the inherited one
|
||||
.prop("attribute-type", valueAttr.type)
|
||||
.prop("attribute-name", valueAttr.name)
|
||||
.prop("value", valueAttr.value)
|
||||
@@ -148,6 +149,8 @@ export default class PromotedAttributesWidget extends TabAwareWidget {
|
||||
cb(filtered);
|
||||
}
|
||||
}]);
|
||||
|
||||
$input.on('autocomplete:selected', e => this.promotedAttributeChanged(e))
|
||||
});
|
||||
}
|
||||
else if (definition.labelType === 'number') {
|
||||
@@ -228,7 +231,7 @@ export default class PromotedAttributesWidget extends TabAwareWidget {
|
||||
.prop("title", "Remove this attribute")
|
||||
.on('click', async () => {
|
||||
if (valueAttr.attributeId) {
|
||||
await server.remove("notes/" + this.noteId + "/attributes/" + valueAttr.attributeId);
|
||||
await server.remove("notes/" + this.noteId + "/attributes/" + valueAttr.attributeId, this.componentId);
|
||||
}
|
||||
|
||||
$tr.remove();
|
||||
@@ -262,8 +265,14 @@ export default class PromotedAttributesWidget extends TabAwareWidget {
|
||||
type: $attr.prop("attribute-type"),
|
||||
name: $attr.prop("attribute-name"),
|
||||
value: value
|
||||
});
|
||||
}, this.componentId);
|
||||
|
||||
$attr.prop("attribute-id", result.attributeId);
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({loadResults}) {
|
||||
if (loadResults.getAttributes(this.componentId).find(attr => attr.isAffecting(this.note))) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import protectedSessionService from "../services/protected_session.js";
|
||||
import TabAwareWidget from "./tab_aware_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="btn-group btn-group-xs">
|
||||
<button type="button"
|
||||
class="btn btn-sm icon-button bx bx-check-shield protect-button"
|
||||
title="Set this note as protected which means it will possible to view and edit this note only after entering password">
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-sm icon-button bx bx-shield-x unprotect-button"
|
||||
title="Set this note as unprotected which will make it viewable and editable without entering password">
|
||||
</button>
|
||||
</div>`;``;
|
||||
|
||||
export default class ProtectedNoteSwitchWidget extends TabAwareWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
refreshWithNote(note) {
|
||||
this.$protectButton.toggleClass("active", note.isProtected);
|
||||
this.$protectButton.prop("disabled", note.isProtected);
|
||||
this.$unprotectButton.toggleClass("active", !note.isProtected);
|
||||
this.$unprotectButton.prop("disabled", !note.isProtected);
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({loadResults}) {
|
||||
if (loadResults.isNoteReloaded(this.noteId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,12 @@ import TabAwareWidget from "./tab_aware_widget.js";
|
||||
const TPL = `
|
||||
<div style="display: inline-flex;">
|
||||
<button class="btn btn-sm icon-button bx bx-play-circle render-button"
|
||||
data-trigger-command="renderActiveNote"
|
||||
title="Render"></button>
|
||||
|
||||
<button class="btn btn-sm icon-button bx bx-play-circle execute-script-button"
|
||||
title="Execute (Ctrl+Enter)"></button>
|
||||
data-trigger-command="runActiveNote"
|
||||
title="Execute"></button>
|
||||
</div>`;
|
||||
|
||||
export default class RunScriptButtonsWidget extends TabAwareWidget {
|
||||
@@ -21,6 +23,12 @@ export default class RunScriptButtonsWidget extends TabAwareWidget {
|
||||
|
||||
refreshWithNote(note) {
|
||||
this.$renderButton.toggle(note.type === 'render');
|
||||
this.$executeScriptButton.toggle(note.mime.startsWith('application/javascript'));
|
||||
this.$executeScriptButton.toggle(note.type === 'code' && note.mime.startsWith('application/javascript'));
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({loadResults}) {
|
||||
if (loadResults.isNoteReloaded(this.noteId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,11 +29,11 @@ const TAB_TPL = `
|
||||
<div class="note-tab-wrapper">
|
||||
<div class="note-tab-title"></div>
|
||||
<div class="note-tab-drag-handle"></div>
|
||||
<div class="note-tab-close kb-in-title" title="Close tab" data-command="closeActiveTab"><span>×</span></div>
|
||||
<div class="note-tab-close" title="Close tab" data-trigger-command="closeActiveTab"><span>×</span></div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
const NEW_TAB_BUTTON_TPL = `<div class="note-new-tab kb-in-title" data-command="openNewTab" title="Add new tab">+</div>`;
|
||||
const NEW_TAB_BUTTON_TPL = `<div class="note-new-tab" data-trigger-command="openNewTab" title="Add new tab">+</div>`;
|
||||
const FILLER_TPL = `<div class="tab-row-filler">
|
||||
<div class="tab-row-border"></div>
|
||||
</div>`;
|
||||
@@ -258,9 +258,9 @@ export default class TabRowWidget extends BasicWidget {
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
items: [
|
||||
{title: "Move this tab to a new window", command: "moveTabToNewWindow", uiIcon: "empty"},
|
||||
{title: "Close all tabs", command: "removeAllTabs", uiIcon: "empty"},
|
||||
{title: "Close all tabs except for this", command: "removeAllTabsExceptForThis", uiIcon: "empty"},
|
||||
{title: "Move this tab to a new window", command: "moveTabToNewWindow", uiIcon: "window-open"},
|
||||
{title: "Close all tabs", command: "removeAllTabs", uiIcon: "x"},
|
||||
{title: "Close all tabs except for this", command: "removeAllTabsExceptForThis", uiIcon: "x"},
|
||||
],
|
||||
selectMenuItemHandler: ({command}) => {
|
||||
this.triggerCommand(command, {tabId});
|
||||
@@ -394,10 +394,13 @@ export default class TabRowWidget extends BasicWidget {
|
||||
this.setupDraggabilly();
|
||||
}
|
||||
|
||||
setTabCloseEvent($tab) {
|
||||
$tab.find('.note-tab-close')
|
||||
.on('click', _ => appContext.tabManager.removeTab($tab.attr('data-tab-id')));
|
||||
closeActiveTabCommand({$el}) {
|
||||
const tabId = $el.closest(".note-tab").attr('data-tab-id');
|
||||
|
||||
appContext.tabManager.removeTab(tabId);
|
||||
}
|
||||
|
||||
setTabCloseEvent($tab) {
|
||||
$tab.on('mousedown', e => {
|
||||
if (e.which === 2) {
|
||||
appContext.tabManager.removeTab($tab.attr('data-tab-id'));
|
||||
@@ -558,8 +561,6 @@ export default class TabRowWidget extends BasicWidget {
|
||||
this.$newTab = $(NEW_TAB_BUTTON_TPL);
|
||||
|
||||
this.$tabContainer.append(this.$newTab);
|
||||
|
||||
this.$newTab.on('click', _ => this.triggerCommand('openNewTab'));
|
||||
}
|
||||
|
||||
setupFiller() {
|
||||
@@ -601,18 +602,23 @@ export default class TabRowWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
updateTab($tab, note) {
|
||||
if (!note || !$tab.length) {
|
||||
if (!$tab.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateTitle($tab, note.title);
|
||||
|
||||
for (const clazz of Array.from($tab[0].classList)) { // create copy to safely iterate over while removing classes
|
||||
if (clazz !== 'note-tab') {
|
||||
$tab.removeClass(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
if (!note) {
|
||||
this.updateTitle($tab, 'New tab');
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateTitle($tab, note.title);
|
||||
|
||||
$tab.addClass(note.getCssClass());
|
||||
$tab.addClass(utils.getNoteTypeClass(note.type));
|
||||
$tab.addClass(utils.getMimeTypeClass(note.mime));
|
||||
|
||||
@@ -7,7 +7,15 @@ const TPL = `
|
||||
<style>
|
||||
.title-bar-buttons {
|
||||
margin-top: 4px;
|
||||
min-width: 100px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.title-bar-buttons button {
|
||||
border: none !important;
|
||||
background: none !important;
|
||||
font-size: 150%;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -62,4 +62,12 @@ export default class AbstractTextTypeWidget extends TypeWidget {
|
||||
|
||||
$el.text(title);
|
||||
}
|
||||
|
||||
refreshIncludedNote($container, noteId) {
|
||||
if ($container) {
|
||||
$container.find(`section[data-note-id="${noteId}"]`).each((_, el) => {
|
||||
this.loadIncludedNote(noteId, $(el));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
import libraryLoader from "../../services/library_loader.js";
|
||||
import bundleService from "../../services/bundle.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import server from "../../services/server.js";
|
||||
import keyboardActionService from "../../services/keyboard_actions.js";
|
||||
import TypeWidget from "./type_widget.js";
|
||||
import keyboardActionService from "../../services/keyboard_actions.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="note-detail-code note-detail-printable">
|
||||
@@ -27,11 +24,8 @@ export default class EditableCodeTypeWidget extends TypeWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$editor = this.$widget.find('.note-detail-code-editor');
|
||||
this.$executeScriptButton = this.$widget.find(".execute-script-button");
|
||||
|
||||
keyboardActionService.setElementActionHandler(this.$widget, 'runActiveNote', () => this.executeCurrentNote());
|
||||
|
||||
this.$executeScriptButton.on('click', () => this.executeCurrentNote());
|
||||
keyboardActionService.setupActionsForElement('code-detail', this.$widget, this);
|
||||
|
||||
this.initialized = this.initEditor();
|
||||
|
||||
@@ -106,26 +100,6 @@ export default class EditableCodeTypeWidget extends TypeWidget {
|
||||
this.codeEditor.focus();
|
||||
}
|
||||
|
||||
async executeCurrentNote() {
|
||||
// ctrl+enter is also used elsewhere so make sure we're running only when appropriate
|
||||
if (this.note.type !== 'code') {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure note is saved so we load latest changes
|
||||
await this.spacedUpdate.updateNowIfNecessary();
|
||||
|
||||
if (this.note.mime.endsWith("env=frontend")) {
|
||||
await bundleService.getAndExecuteBundle(this.noteId);
|
||||
}
|
||||
|
||||
if (this.note.mime.endsWith("env=backend")) {
|
||||
await server.post('script/run/' + this.noteId);
|
||||
}
|
||||
|
||||
toastService.showMessage("Note executed");
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if (this.codeEditor) {
|
||||
this.spacedUpdate.allowUpdateWithoutChange(() => {
|
||||
|
||||
@@ -23,6 +23,7 @@ const mentionSetup = {
|
||||
row.text = row.name = row.noteTitle;
|
||||
row.id = '@' + row.text;
|
||||
row.link = '#' + row.path;
|
||||
row.notePath = row.path;
|
||||
}
|
||||
|
||||
res(rows);
|
||||
@@ -256,4 +257,8 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
this.textEditor.model.insertContent(imageElement, this.textEditor.model.document.selection);
|
||||
} );
|
||||
}
|
||||
|
||||
async refreshIncludedNoteEvent({noteId}) {
|
||||
this.refreshIncludedNote(this.$editor, noteId);
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,23 @@ import TypeWidget from "./type_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="note-detail-file note-detail-printable">
|
||||
<style>
|
||||
.file-table td {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
</style>
|
||||
|
||||
<table class="file-table">
|
||||
<tr>
|
||||
<th nowrap>Note ID:</th>
|
||||
<th>Note ID:</th>
|
||||
<td class="file-note-id"></td>
|
||||
<th nowrap>Original file name:</th>
|
||||
<th>Original file name:</th>
|
||||
<td class="file-filename"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th nowrap>File type:</th>
|
||||
<th>File type:</th>
|
||||
<td class="file-filetype"></td>
|
||||
<th nowrap>File size:</th>
|
||||
<th>File size:</th>
|
||||
<td class="file-filesize"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -22,6 +22,10 @@ const TPL = `
|
||||
.note-detail-readonly-text p:first-child, .note-detail-text::before {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.note-detail-readonly-text img {
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="alert alert-warning no-print">
|
||||
@@ -77,4 +81,8 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
|
||||
this.loadIncludedNote(noteId, $(el));
|
||||
});
|
||||
}
|
||||
|
||||
async refreshIncludedNoteEvent({noteId}) {
|
||||
this.refreshIncludedNote(this.$content, noteId);
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,6 @@ export default class RenderTypeWidget extends TypeWidget {
|
||||
this.$widget = $(TPL);
|
||||
this.$noteDetailRenderHelp = this.$widget.find('.note-detail-render-help');
|
||||
this.$noteDetailRenderContent = this.$widget.find('.note-detail-render-content');
|
||||
this.$renderButton = this.$widget.find('.render-button');
|
||||
|
||||
this.$renderButton.on('click', () => this.refresh());
|
||||
|
||||
return this.$widget;
|
||||
}
|
||||
@@ -46,4 +43,10 @@ export default class RenderTypeWidget extends TypeWidget {
|
||||
cleanup() {
|
||||
this.$noteDetailRenderContent.empty();
|
||||
}
|
||||
|
||||
renderActiveNoteEvent() {
|
||||
if (this.tabContext.isActive()) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,8 @@
|
||||
|
||||
.note-detail-image {
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.note-detail-image-view {
|
||||
|
||||
@@ -113,71 +113,6 @@ span.fancytree-node.muted { opacity: 0.6; }
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
ul.fancytree-container {
|
||||
outline: none !important;
|
||||
background-color: inherit !important;
|
||||
}
|
||||
|
||||
.fancytree-custom-icon {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
span.fancytree-title {
|
||||
color: inherit !important;
|
||||
background: inherit !important;
|
||||
}
|
||||
|
||||
span.fancytree-node.protected > span.fancytree-custom-icon {
|
||||
filter: drop-shadow(2px 2px 2px var(--main-text-color));
|
||||
}
|
||||
|
||||
span.fancytree-node.multiple-parents .fancytree-title::after {
|
||||
content: " *"
|
||||
}
|
||||
|
||||
span.fancytree-node.fancytree-active-clone:not(.fancytree-active) .fancytree-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* first nesting level has lower left padding to avoid extra left padding. Other levels are not affected */
|
||||
.ui-fancytree > li > ul {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
span.fancytree-active .fancytree-title {
|
||||
font-weight: bold;
|
||||
border-color: var(--main-border-color) !important;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
span.fancytree-active:not(.fancytree-focused) .fancytree-title {
|
||||
border-style: dashed !important;
|
||||
}
|
||||
|
||||
span.fancytree-focused .fancytree-title, span.fancytree-focused.fancytree-selected .fancytree-title {
|
||||
color: var(--active-item-text-color) !important;
|
||||
background-color: var(--active-item-background-color) !important;
|
||||
border-color: var(--main-background-color) !important; /* invisible border */
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
span.fancytree-selected .fancytree-title {
|
||||
color: var(--hover-item-text-color) !important;
|
||||
background-color: var(--hover-item-background-color) !important;
|
||||
border-color: var(--main-background-color) !important; /* invisible border */
|
||||
border-radius: 5px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
span.fancytree-node:hover span.fancytree-title {
|
||||
border-color: var(--main-border-color) !important;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
span.fancytree-node.archived {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.ui-autocomplete {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const anonymization = require('../../services/anonymization');
|
||||
|
||||
async function anonymize() {
|
||||
await anonymization.anonymize();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
anonymize
|
||||
};
|
||||
@@ -101,7 +101,7 @@ async function updateNoteAttributes(req) {
|
||||
|| (attribute.type === 'relation' && attribute.value !== attributeEntity.value)) {
|
||||
|
||||
if (attribute.type !== 'relation' || !!attribute.value.trim()) {
|
||||
const newAttribute = attribute.createClone(attribute.type, attribute.name, attribute.value);
|
||||
const newAttribute = attributeEntity.createClone(attribute.type, attribute.name, attribute.value);
|
||||
await newAttribute.save();
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ async function updateNoteAttributes(req) {
|
||||
}
|
||||
|
||||
const note = await repository.getNote(noteId);
|
||||
note.invalidateAttributeCache();
|
||||
|
||||
return await note.getAttributes();
|
||||
}
|
||||
|
||||
@@ -14,25 +14,33 @@ const TaskContext = require('../../services/task_context');
|
||||
*/
|
||||
|
||||
async function moveBranchToParent(req) {
|
||||
const {branchId, parentNoteId} = req.params;
|
||||
const {branchId, parentBranchId} = req.params;
|
||||
|
||||
const parentBranch = await repository.getBranch(parentBranchId);
|
||||
const branchToMove = await repository.getBranch(branchId);
|
||||
|
||||
if (branchToMove.parentNoteId === parentNoteId) {
|
||||
if (!parentBranch || !branchToMove) {
|
||||
return [400, `One or both branches ${branchId}, ${parentBranchId} have not been found`];
|
||||
}
|
||||
|
||||
if (branchToMove.parentNoteId === parentBranch.noteId) {
|
||||
return { success: true }; // no-op
|
||||
}
|
||||
|
||||
const validationResult = await treeService.validateParentChild(parentNoteId, branchToMove.noteId, branchId);
|
||||
const validationResult = await treeService.validateParentChild(parentBranch.noteId, branchToMove.noteId, branchId);
|
||||
|
||||
if (!validationResult.success) {
|
||||
return [200, validationResult];
|
||||
}
|
||||
|
||||
const maxNotePos = await sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [parentNoteId]);
|
||||
const maxNotePos = await sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [parentBranch.noteId]);
|
||||
const newNotePos = maxNotePos === null ? 0 : maxNotePos + 10;
|
||||
|
||||
const newBranch = branchToMove.createClone(parentNoteId, newNotePos);
|
||||
newBranch.isExpanded = true;
|
||||
// expanding so that the new placement of the branch is immediately visible
|
||||
parentBranch.isExpanded = true;
|
||||
await parentBranch.save();
|
||||
|
||||
const newBranch = branchToMove.createClone(parentBranch.noteId, newNotePos);
|
||||
await newBranch.save();
|
||||
|
||||
branchToMove.isDeleted = true;
|
||||
@@ -114,8 +122,35 @@ async function moveBranchAfterNote(req) {
|
||||
async function setExpanded(req) {
|
||||
const {branchId, expanded} = req.params;
|
||||
|
||||
if (branchId !== 'root') {
|
||||
await sql.execute("UPDATE branches SET isExpanded = ? WHERE branchId = ?", [expanded, branchId]);
|
||||
// we don't sync expanded label
|
||||
// also this does not trigger updates to the frontend, this would trigger too many reloads
|
||||
}
|
||||
}
|
||||
|
||||
async function setExpandedForSubtree(req) {
|
||||
const {branchId, expanded} = req.params;
|
||||
|
||||
let branchIds = await sql.getColumn(`
|
||||
WITH RECURSIVE
|
||||
tree(branchId, noteId) AS (
|
||||
SELECT branchId, noteId FROM branches WHERE branchId = ?
|
||||
UNION
|
||||
SELECT branches.branchId, branches.noteId FROM branches
|
||||
JOIN tree ON branches.parentNoteId = tree.noteId
|
||||
WHERE branches.isDeleted = 0
|
||||
)
|
||||
SELECT branchId FROM tree`, [branchId]);
|
||||
|
||||
// root is always expanded
|
||||
branchIds = branchIds.filter(branchId => branchId !== 'root');
|
||||
|
||||
await sql.executeMany(`UPDATE branches SET isExpanded = ${expanded} WHERE branchId IN (???)`, branchIds);
|
||||
|
||||
return {
|
||||
branchIds
|
||||
};
|
||||
}
|
||||
|
||||
async function deleteBranch(req) {
|
||||
@@ -149,6 +184,7 @@ module.exports = {
|
||||
moveBranchBeforeNote,
|
||||
moveBranchAfterNote,
|
||||
setExpanded,
|
||||
setExpandedForSubtree,
|
||||
deleteBranch,
|
||||
setPrefix
|
||||
};
|
||||
@@ -65,7 +65,11 @@ async function addClipping(req) {
|
||||
}
|
||||
|
||||
async function createNote(req) {
|
||||
const {title, content, pageUrl, images, clipType} = req.body;
|
||||
let {title, content, pageUrl, images, clipType} = req.body;
|
||||
|
||||
if (!title || !title.trim()) {
|
||||
title = "Clipped note from " + pageUrl;
|
||||
}
|
||||
|
||||
log.info(`Creating clipped note from ${pageUrl}`);
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
const cloningService = require('../../services/cloning');
|
||||
|
||||
async function cloneNoteToParent(req) {
|
||||
const {noteId, parentNoteId} = req.params;
|
||||
const {noteId, parentBranchId} = req.params;
|
||||
const {prefix} = req.body;
|
||||
|
||||
return await cloningService.cloneNoteToParent(noteId, parentNoteId, prefix);
|
||||
return await cloningService.cloneNoteToParent(noteId, parentBranchId, prefix);
|
||||
}
|
||||
|
||||
async function cloneNoteAfter(req) {
|
||||
|
||||
@@ -2,8 +2,19 @@
|
||||
|
||||
const sql = require('../../services/sql');
|
||||
const log = require('../../services/log');
|
||||
const backupService = require('../../services/backup');
|
||||
const consistencyChecksService = require('../../services/consistency_checks');
|
||||
|
||||
async function anonymize() {
|
||||
return await backupService.anonymize();
|
||||
}
|
||||
|
||||
async function backupDatabase() {
|
||||
return {
|
||||
backupFile: await backupService.backupNow("now")
|
||||
};
|
||||
}
|
||||
|
||||
async function vacuumDatabase() {
|
||||
await sql.execute("VACUUM");
|
||||
|
||||
@@ -15,6 +26,8 @@ async function findAndFixConsistencyIssues() {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
backupDatabase,
|
||||
vacuumDatabase,
|
||||
findAndFixConsistencyIssues
|
||||
findAndFixConsistencyIssues,
|
||||
anonymize
|
||||
};
|
||||
@@ -11,6 +11,14 @@ async function exportBranch(req, res) {
|
||||
const {branchId, type, format, version, taskId} = req.params;
|
||||
const branch = await repository.getBranch(branchId);
|
||||
|
||||
if (!branch) {
|
||||
const message = `Cannot export branch ${branchId} since it does not exist.`;
|
||||
log.error(message);
|
||||
|
||||
res.status(500).send(message);
|
||||
return;
|
||||
}
|
||||
|
||||
const taskContext = new TaskContext(taskId, 'export');
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
const noteService = require('../../services/notes');
|
||||
const protectedSessionService = require('../../services/protected_session');
|
||||
const repository = require('../../services/repository');
|
||||
const utils = require('../../services/utils');
|
||||
@@ -45,7 +44,9 @@ async function downloadNoteFile(noteId, res, contentDisposition = true) {
|
||||
if (contentDisposition) {
|
||||
// (one) reason we're not using the originFileName (available as label) is that it's not
|
||||
// available for older note revisions and thus would be inconsistent
|
||||
res.setHeader('Content-Disposition', utils.getContentDisposition(note.title || "untitled"));
|
||||
const filename = utils.formatDownloadTitle(note.title, note.type, note.mime);
|
||||
|
||||
res.setHeader('Content-Disposition', utils.getContentDisposition(filename));
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', note.mime);
|
||||
|
||||
@@ -16,7 +16,7 @@ const ApiToken = require('../../entities/api_token');
|
||||
|
||||
async function loginSync(req) {
|
||||
if (!await sqlInit.schemaExists()) {
|
||||
return [400, { message: "DB schema does not exist, can't sync." }];
|
||||
return [500, { message: "DB schema does not exist, can't sync." }];
|
||||
}
|
||||
|
||||
const timestampStr = req.body.timestamp;
|
||||
@@ -27,7 +27,7 @@ async function loginSync(req) {
|
||||
|
||||
// login token is valid for 5 minutes
|
||||
if (Math.abs(timestamp.getTime() - now.getTime()) > 5 * 60 * 1000) {
|
||||
return [400, { message: 'Auth request time is out of sync, please check that both client and server have correct time.' }];
|
||||
return [401, { message: 'Auth request time is out of sync, please check that both client and server have correct time.' }];
|
||||
}
|
||||
|
||||
const syncVersion = req.body.syncVersion;
|
||||
@@ -42,7 +42,7 @@ async function loginSync(req) {
|
||||
const givenHash = req.body.hash;
|
||||
|
||||
if (expectedHash !== givenHash) {
|
||||
return [400, { message: "Sync login credentials are incorrect." }];
|
||||
return [400, { message: "Sync login credentials are incorrect. It looks like you're trying to sync two different initialized documents which is not possible." }];
|
||||
}
|
||||
|
||||
req.session.loggedIn = true;
|
||||
@@ -68,7 +68,7 @@ async function loginToProtectedSession(req) {
|
||||
const protectedSessionId = protectedSessionService.setDataKey(decryptedDataKey);
|
||||
|
||||
// this is set here so that event handlers have access to the protected session
|
||||
cls.namespace.set('protectedSessionId', protectedSessionId);
|
||||
cls.set('protectedSessionId', protectedSessionId);
|
||||
|
||||
await eventService.emit(eventService.ENTER_PROTECTED_SESSION);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const repository = require('../../services/repository');
|
||||
const noteCacheService = require('../../services/note_cache');
|
||||
const protectedSessionService = require('../../services/protected_session');
|
||||
const noteRevisionService = require('../../services/note_revisions');
|
||||
const utils = require('../../services/utils');
|
||||
const path = require('path');
|
||||
|
||||
@@ -37,13 +38,7 @@ async function getNoteRevision(req) {
|
||||
* @return {string}
|
||||
*/
|
||||
function getRevisionFilename(noteRevision) {
|
||||
let filename = noteRevision.title || "untitled";
|
||||
|
||||
if (noteRevision.type === 'text') {
|
||||
filename += '.html';
|
||||
} else if (['relation-map', 'search'].includes(noteRevision.type)) {
|
||||
filename += '.json';
|
||||
}
|
||||
let filename = utils.formatDownloadTitle(noteRevision.title, noteRevision.type, noteRevision.mime);
|
||||
|
||||
const extension = path.extname(filename);
|
||||
const date = noteRevision.dateCreated
|
||||
@@ -109,6 +104,20 @@ async function eraseNoteRevision(req) {
|
||||
}
|
||||
}
|
||||
|
||||
async function restoreNoteRevision(req) {
|
||||
const noteRevision = await repository.getNoteRevision(req.params.noteRevisionId);
|
||||
|
||||
if (noteRevision && !noteRevision.isErased) {
|
||||
const note = await noteRevision.getNote();
|
||||
|
||||
await noteRevisionService.createNoteRevision(note);
|
||||
|
||||
note.title = noteRevision.title;
|
||||
await note.setContent(await noteRevision.getContent());
|
||||
await note.save();
|
||||
}
|
||||
}
|
||||
|
||||
async function getEditedNotesOnDate(req) {
|
||||
const date = utils.sanitizeSql(req.params.date);
|
||||
|
||||
@@ -141,5 +150,6 @@ module.exports = {
|
||||
downloadNoteRevision,
|
||||
getEditedNotesOnDate,
|
||||
eraseAllNoteRevisions,
|
||||
eraseNoteRevision
|
||||
eraseNoteRevision,
|
||||
restoreNoteRevision
|
||||
};
|
||||
@@ -164,10 +164,16 @@ async function changeTitle(req) {
|
||||
return [400, `Note ${noteId} is not available for change`];
|
||||
}
|
||||
|
||||
const noteTitleChanged = note.title !== title;
|
||||
|
||||
note.title = title;
|
||||
|
||||
await note.save();
|
||||
|
||||
if (noteTitleChanged) {
|
||||
await noteService.triggerNoteTitleChanged(note);
|
||||
}
|
||||
|
||||
return note;
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,9 @@ async function getUserThemes() {
|
||||
function isAllowed(name) {
|
||||
return ALLOWED_OPTIONS.has(name)
|
||||
|| name.startsWith("keyboardShortcuts")
|
||||
|| name.endsWith("Collapsed");
|
||||
|| name.endsWith("Collapsed")
|
||||
|| name.startsWith("hideArchivedNotes")
|
||||
|| name.startsWith("hideIncludedImages");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -8,34 +8,9 @@ const noteCacheService = require('../../services/note_cache');
|
||||
async function getRecentChanges(req) {
|
||||
const {ancestorNoteId} = req.params;
|
||||
|
||||
const noteRows = await sql.getRows(
|
||||
`
|
||||
SELECT * FROM (
|
||||
SELECT note_revisions.noteId,
|
||||
note_revisions.noteRevisionId,
|
||||
note_revisions.dateLastEdited AS date
|
||||
FROM note_revisions
|
||||
ORDER BY note_revisions.dateLastEdited DESC
|
||||
)
|
||||
UNION ALL SELECT * FROM (
|
||||
SELECT
|
||||
notes.noteId,
|
||||
NULL AS noteRevisionId,
|
||||
dateModified AS date
|
||||
FROM notes
|
||||
ORDER BY dateModified DESC
|
||||
)
|
||||
ORDER BY date DESC`);
|
||||
let recentChanges = [];
|
||||
|
||||
const recentChanges = [];
|
||||
|
||||
for (const noteRow of noteRows) {
|
||||
if (!noteCacheService.isInAncestor(noteRow.noteId, ancestorNoteId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (noteRow.noteRevisionId) {
|
||||
recentChanges.push(await sql.getRow(`
|
||||
const noteRevisions = await sql.getRows(`
|
||||
SELECT
|
||||
notes.noteId,
|
||||
notes.isDeleted AS current_isDeleted,
|
||||
@@ -44,14 +19,19 @@ async function getRecentChanges(req) {
|
||||
notes.title AS current_title,
|
||||
notes.isProtected AS current_isProtected,
|
||||
note_revisions.title,
|
||||
note_revisions.utcDateCreated AS utcDate,
|
||||
note_revisions.dateCreated AS date
|
||||
FROM
|
||||
note_revisions
|
||||
JOIN notes USING(noteId)
|
||||
WHERE noteRevisionId = ?`, [noteRow.noteRevisionId]));
|
||||
JOIN notes USING(noteId)`);
|
||||
|
||||
for (const noteRevision of noteRevisions) {
|
||||
if (noteCacheService.isInAncestor(noteRevision.noteId, ancestorNoteId)) {
|
||||
recentChanges.push(noteRevision);
|
||||
}
|
||||
else {
|
||||
recentChanges.push(await sql.getRow(`
|
||||
}
|
||||
|
||||
const notes = await sql.getRows(`
|
||||
SELECT
|
||||
notes.noteId,
|
||||
notes.isDeleted AS current_isDeleted,
|
||||
@@ -60,16 +40,22 @@ async function getRecentChanges(req) {
|
||||
notes.title AS current_title,
|
||||
notes.isProtected AS current_isProtected,
|
||||
notes.title,
|
||||
notes.dateModified AS date
|
||||
notes.utcDateCreated AS utcDate,
|
||||
notes.dateCreated AS date
|
||||
FROM
|
||||
notes
|
||||
WHERE noteId = ?`, [noteRow.noteId]));
|
||||
notes`);
|
||||
|
||||
for (const note of notes) {
|
||||
if (noteCacheService.isInAncestor(note.noteId, ancestorNoteId)) {
|
||||
recentChanges.push(note);
|
||||
}
|
||||
}
|
||||
|
||||
if (recentChanges.length >= 200) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
recentChanges.sort((a, b) => a.utcDate > b.utcDate ? -1 : 1);
|
||||
|
||||
recentChanges = recentChanges.slice(0, Math.min(500, recentChanges.length));
|
||||
|
||||
console.log(recentChanges);
|
||||
|
||||
for (const change of recentChanges) {
|
||||
if (change.current_isProtected) {
|
||||
|
||||
@@ -55,6 +55,8 @@ async function checkSync() {
|
||||
}
|
||||
|
||||
async function syncNow() {
|
||||
log.info("Received request to trigger sync now.");
|
||||
|
||||
return await syncService.sync();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,11 @@ const repository = require('../services/repository');
|
||||
const log = require('../services/log');
|
||||
const fileUploadService = require('./api/files.js');
|
||||
const scriptService = require('../services/script');
|
||||
const cls = require('../services/cls');
|
||||
|
||||
function register(router) {
|
||||
// explicitly no CSRF middleware since it's meant to allow integration from external services
|
||||
|
||||
router.all('/custom/:path*', async (req, res, next) => {
|
||||
async function handleRequest(req, res) {
|
||||
// express puts content after first slash into 0 index element
|
||||
|
||||
const path = req.params.path + req.params[0];
|
||||
|
||||
const attrs = await repository.getEntities("SELECT * FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name IN ('customRequestHandler', 'customResourceProvider')");
|
||||
@@ -60,6 +59,16 @@ function register(router) {
|
||||
|
||||
log.info(message);
|
||||
res.status(404).send(message);
|
||||
}
|
||||
|
||||
function register(router) {
|
||||
// explicitly no CSRF middleware since it's meant to allow integration from external services
|
||||
|
||||
router.all('/custom/:path*', async (req, res, next) => {
|
||||
cls.namespace.bindEmitter(req);
|
||||
cls.namespace.bindEmitter(res);
|
||||
|
||||
cls.init(() => handleRequest(req, res));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user