mirror of
https://github.com/zadam/trilium.git
synced 2025-10-27 16:26:31 +01:00
Compare commits
51 Commits
v0.10.0-be
...
v0.12.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8b9c7d936 | ||
|
|
d57057ba28 | ||
|
|
66cee8daa4 | ||
|
|
afd7df0942 | ||
|
|
bd6ae33d32 | ||
|
|
70660a0d68 | ||
|
|
cdad18551a | ||
|
|
592c51d1a5 | ||
|
|
6a57b8a7e7 | ||
|
|
7a94e21c54 | ||
|
|
5b43f321e2 | ||
|
|
a4eafb934f | ||
|
|
7b59a665dd | ||
|
|
3d15450ffc | ||
|
|
b0c6d52461 | ||
|
|
2dc16dd29f | ||
|
|
d8924c536b | ||
|
|
3ebbf2cc46 | ||
|
|
f4079604c9 | ||
|
|
1f96a6beab | ||
|
|
b277a250e5 | ||
|
|
5b0e1a644d | ||
|
|
6bb3cfa9a3 | ||
|
|
9720868f5a | ||
|
|
8d8ee2a87a | ||
|
|
542e82ee5d | ||
|
|
0104b19502 | ||
|
|
120888b53e | ||
|
|
d2e2caed62 | ||
|
|
63066802a8 | ||
|
|
6128bb4ff3 | ||
|
|
982796255d | ||
|
|
36b15f474d | ||
|
|
13f71f8967 | ||
|
|
64336ffbee | ||
|
|
b09463d1b2 | ||
|
|
b5e6f46b9c | ||
|
|
08af4a0465 | ||
|
|
8c5df6321f | ||
|
|
d19f044961 | ||
|
|
e378d9f645 | ||
|
|
39dc0f71b4 | ||
|
|
0cef5c6b8c | ||
|
|
9b5a44cef4 | ||
|
|
29769ed91d | ||
|
|
867d794e17 | ||
|
|
fdd8458336 | ||
|
|
a0bec22e96 | ||
|
|
5aeb5cd214 | ||
|
|
e827ddffb9 | ||
|
|
98f80998b9 |
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<dataSource name="document.db">
|
||||
<database-model serializer="dbm" rdbms="SQLITE" format-version="4.7">
|
||||
<root id="1"/>
|
||||
<database-model serializer="dbm" rdbms="SQLITE" format-version="4.8">
|
||||
<root id="1">
|
||||
<ServerVersion>3.16.1</ServerVersion>
|
||||
</root>
|
||||
<schema id="2" parent="1" name="main">
|
||||
<Current>1</Current>
|
||||
<Visible>1</Visible>
|
||||
@@ -107,8 +109,7 @@
|
||||
<index id="35" parent="7" name="IDX_branches_noteId_parentNoteId">
|
||||
<ColNames>noteId
|
||||
parentNoteId</ColNames>
|
||||
<ColumnCollations>
|
||||
</ColumnCollations>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="36" parent="7" name="IDX_branches_noteId">
|
||||
<ColNames>noteId</ColNames>
|
||||
@@ -142,445 +143,449 @@ parentNoteId</ColNames>
|
||||
<ColNames>id</ColNames>
|
||||
<Primary>1</Primary>
|
||||
</key>
|
||||
<foreign-key id="43" parent="8">
|
||||
<ColNames>noteId</ColNames>
|
||||
<RefTableName>notes</RefTableName>
|
||||
<RefColNames>noteId</RefColNames>
|
||||
</foreign-key>
|
||||
<column id="44" parent="9" name="imageId">
|
||||
<column id="43" parent="9" name="imageId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="45" parent="9" name="format">
|
||||
<column id="44" parent="9" name="format">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="46" parent="9" name="checksum">
|
||||
<column id="45" parent="9" name="checksum">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="47" parent="9" name="name">
|
||||
<column id="46" parent="9" name="name">
|
||||
<Position>4</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="48" parent="9" name="data">
|
||||
<column id="47" parent="9" name="data">
|
||||
<Position>5</Position>
|
||||
<DataType>BLOB|0s</DataType>
|
||||
</column>
|
||||
<column id="49" parent="9" name="isDeleted">
|
||||
<column id="48" parent="9" name="isDeleted">
|
||||
<Position>6</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="50" parent="9" name="dateModified">
|
||||
<column id="49" parent="9" name="dateModified">
|
||||
<Position>7</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="51" parent="9" name="dateCreated">
|
||||
<column id="50" parent="9" name="dateCreated">
|
||||
<Position>8</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="52" parent="9" name="sqlite_autoindex_images_1">
|
||||
<index id="51" parent="9" name="sqlite_autoindex_images_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>imageId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<key id="53" parent="9">
|
||||
<key id="52" parent="9">
|
||||
<ColNames>imageId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="54" parent="10" name="labelId">
|
||||
<column id="53" parent="10" name="labelId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="55" parent="10" name="noteId">
|
||||
<column id="54" parent="10" name="noteId">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="56" parent="10" name="name">
|
||||
<column id="55" parent="10" name="name">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="57" parent="10" name="value">
|
||||
<column id="56" parent="10" name="value">
|
||||
<Position>4</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>''</DefaultExpression>
|
||||
</column>
|
||||
<column id="58" parent="10" name="position">
|
||||
<column id="57" parent="10" name="position">
|
||||
<Position>5</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="59" parent="10" name="dateCreated">
|
||||
<column id="58" parent="10" name="dateCreated">
|
||||
<Position>6</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="60" parent="10" name="dateModified">
|
||||
<column id="59" parent="10" name="dateModified">
|
||||
<Position>7</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="61" parent="10" name="isDeleted">
|
||||
<column id="60" parent="10" name="isDeleted">
|
||||
<Position>8</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="62" parent="10" name="sqlite_autoindex_labels_1">
|
||||
<index id="61" parent="10" name="sqlite_autoindex_labels_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>labelId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="63" parent="10" name="IDX_labels_noteId">
|
||||
<index id="62" parent="10" name="IDX_labels_noteId">
|
||||
<ColNames>noteId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="64" parent="10" name="IDX_labels_name_value">
|
||||
<index id="63" parent="10" name="IDX_labels_name_value">
|
||||
<ColNames>name
|
||||
value</ColNames>
|
||||
<ColumnCollations>
|
||||
</ColumnCollations>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<key id="65" parent="10">
|
||||
<key id="64" parent="10">
|
||||
<ColNames>labelId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="66" parent="11" name="noteImageId">
|
||||
<column id="65" parent="11" name="noteImageId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="67" parent="11" name="noteId">
|
||||
<column id="66" parent="11" name="noteId">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="68" parent="11" name="imageId">
|
||||
<column id="67" parent="11" name="imageId">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="69" parent="11" name="isDeleted">
|
||||
<column id="68" parent="11" name="isDeleted">
|
||||
<Position>4</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="70" parent="11" name="dateModified">
|
||||
<column id="69" parent="11" name="dateModified">
|
||||
<Position>5</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="71" parent="11" name="dateCreated">
|
||||
<column id="70" parent="11" name="dateCreated">
|
||||
<Position>6</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="72" parent="11" name="sqlite_autoindex_note_images_1">
|
||||
<index id="71" parent="11" name="sqlite_autoindex_note_images_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>noteImageId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="73" parent="11" name="IDX_note_images_noteId_imageId">
|
||||
<index id="72" parent="11" name="IDX_note_images_noteId_imageId">
|
||||
<ColNames>noteId
|
||||
imageId</ColNames>
|
||||
<ColumnCollations>
|
||||
</ColumnCollations>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="74" parent="11" name="IDX_note_images_noteId">
|
||||
<index id="73" parent="11" name="IDX_note_images_noteId">
|
||||
<ColNames>noteId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="75" parent="11" name="IDX_note_images_imageId">
|
||||
<index id="74" parent="11" name="IDX_note_images_imageId">
|
||||
<ColNames>imageId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<key id="76" parent="11">
|
||||
<key id="75" parent="11">
|
||||
<ColNames>noteImageId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_note_images_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="77" parent="12" name="noteRevisionId">
|
||||
<column id="76" parent="12" name="noteRevisionId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="78" parent="12" name="noteId">
|
||||
<column id="77" parent="12" name="noteId">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="79" parent="12" name="title">
|
||||
<column id="78" parent="12" name="title">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
</column>
|
||||
<column id="80" parent="12" name="content">
|
||||
<column id="79" parent="12" name="content">
|
||||
<Position>4</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
</column>
|
||||
<column id="81" parent="12" name="isProtected">
|
||||
<column id="80" parent="12" name="isProtected">
|
||||
<Position>5</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="82" parent="12" name="dateModifiedFrom">
|
||||
<column id="81" parent="12" name="dateModifiedFrom">
|
||||
<Position>6</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="83" parent="12" name="dateModifiedTo">
|
||||
<column id="82" parent="12" name="dateModifiedTo">
|
||||
<Position>7</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="84" parent="12" name="sqlite_autoindex_note_revisions_1">
|
||||
<column id="83" parent="12" name="type">
|
||||
<Position>8</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>''</DefaultExpression>
|
||||
</column>
|
||||
<column id="84" parent="12" name="mime">
|
||||
<Position>9</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>''</DefaultExpression>
|
||||
</column>
|
||||
<index id="85" parent="12" name="sqlite_autoindex_note_revisions_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>noteRevisionId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="85" parent="12" name="IDX_note_revisions_noteId">
|
||||
<index id="86" parent="12" name="IDX_note_revisions_noteId">
|
||||
<ColNames>noteId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="86" parent="12" name="IDX_note_revisions_dateModifiedFrom">
|
||||
<index id="87" parent="12" name="IDX_note_revisions_dateModifiedFrom">
|
||||
<ColNames>dateModifiedFrom</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<index id="87" parent="12" name="IDX_note_revisions_dateModifiedTo">
|
||||
<index id="88" parent="12" name="IDX_note_revisions_dateModifiedTo">
|
||||
<ColNames>dateModifiedTo</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<key id="88" parent="12">
|
||||
<key id="89" parent="12">
|
||||
<ColNames>noteRevisionId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="89" parent="13" name="noteId">
|
||||
<column id="90" parent="13" name="noteId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="90" parent="13" name="title">
|
||||
<column id="91" parent="13" name="title">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
</column>
|
||||
<column id="91" parent="13" name="content">
|
||||
<column id="92" parent="13" name="content">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
</column>
|
||||
<column id="92" parent="13" name="isProtected">
|
||||
<column id="93" parent="13" name="isProtected">
|
||||
<Position>4</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="93" parent="13" name="isDeleted">
|
||||
<column id="94" parent="13" name="isDeleted">
|
||||
<Position>5</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<column id="94" parent="13" name="dateCreated">
|
||||
<column id="95" parent="13" name="dateCreated">
|
||||
<Position>6</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="95" parent="13" name="dateModified">
|
||||
<column id="96" parent="13" name="dateModified">
|
||||
<Position>7</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="96" parent="13" name="type">
|
||||
<column id="97" parent="13" name="type">
|
||||
<Position>8</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>'text'</DefaultExpression>
|
||||
</column>
|
||||
<column id="97" parent="13" name="mime">
|
||||
<column id="98" parent="13" name="mime">
|
||||
<Position>9</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>'text/html'</DefaultExpression>
|
||||
</column>
|
||||
<index id="98" parent="13" name="sqlite_autoindex_notes_1">
|
||||
<index id="99" parent="13" name="sqlite_autoindex_notes_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>noteId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="99" parent="13" name="IDX_notes_isDeleted">
|
||||
<index id="100" parent="13" name="IDX_notes_isDeleted">
|
||||
<ColNames>isDeleted</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<key id="100" parent="13">
|
||||
<key id="101" parent="13">
|
||||
<ColNames>noteId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="101" parent="14" name="name">
|
||||
<column id="102" parent="14" name="name">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="102" parent="14" name="value">
|
||||
<column id="103" parent="14" name="value">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
</column>
|
||||
<column id="103" parent="14" name="dateModified">
|
||||
<column id="104" parent="14" name="dateModified">
|
||||
<Position>3</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
</column>
|
||||
<column id="104" parent="14" name="isSynced">
|
||||
<column id="105" parent="14" name="isSynced">
|
||||
<Position>4</Position>
|
||||
<DataType>INTEGER|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<DefaultExpression>0</DefaultExpression>
|
||||
</column>
|
||||
<index id="105" parent="14" name="sqlite_autoindex_options_1">
|
||||
<index id="106" parent="14" name="sqlite_autoindex_options_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>name</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<key id="106" parent="14">
|
||||
<key id="107" parent="14">
|
||||
<ColNames>name</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="107" parent="15" name="branchId">
|
||||
<column id="108" parent="15" name="branchId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="108" parent="15" name="notePath">
|
||||
<column id="109" parent="15" name="notePath">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="109" parent="15" name="dateAccessed">
|
||||
<column id="110" parent="15" name="dateAccessed">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="110" parent="15" name="isDeleted">
|
||||
<column id="111" parent="15" name="isDeleted">
|
||||
<Position>4</Position>
|
||||
<DataType>INT|0s</DataType>
|
||||
</column>
|
||||
<index id="111" parent="15" name="sqlite_autoindex_recent_notes_1">
|
||||
<index id="112" parent="15" name="sqlite_autoindex_recent_notes_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>branchId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<key id="112" parent="15">
|
||||
<key id="113" parent="15">
|
||||
<ColNames>branchId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="113" parent="16" name="sourceId">
|
||||
<column id="114" parent="16" name="sourceId">
|
||||
<Position>1</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="114" parent="16" name="dateCreated">
|
||||
<column id="115" parent="16" name="dateCreated">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="115" parent="16" name="sqlite_autoindex_source_ids_1">
|
||||
<index id="116" parent="16" name="sqlite_autoindex_source_ids_1">
|
||||
<NameSurrogate>1</NameSurrogate>
|
||||
<ColNames>sourceId</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<key id="116" parent="16">
|
||||
<key id="117" parent="16">
|
||||
<ColNames>sourceId</ColNames>
|
||||
<Primary>1</Primary>
|
||||
<UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName>
|
||||
</key>
|
||||
<column id="117" parent="17" name="type">
|
||||
<column id="118" parent="17" name="type">
|
||||
<Position>1</Position>
|
||||
<DataType>text|0s</DataType>
|
||||
</column>
|
||||
<column id="118" parent="17" name="name">
|
||||
<column id="119" parent="17" name="name">
|
||||
<Position>2</Position>
|
||||
<DataType>text|0s</DataType>
|
||||
</column>
|
||||
<column id="119" parent="17" name="tbl_name">
|
||||
<column id="120" parent="17" name="tbl_name">
|
||||
<Position>3</Position>
|
||||
<DataType>text|0s</DataType>
|
||||
</column>
|
||||
<column id="120" parent="17" name="rootpage">
|
||||
<column id="121" parent="17" name="rootpage">
|
||||
<Position>4</Position>
|
||||
<DataType>integer|0s</DataType>
|
||||
</column>
|
||||
<column id="121" parent="17" name="sql">
|
||||
<column id="122" parent="17" name="sql">
|
||||
<Position>5</Position>
|
||||
<DataType>text|0s</DataType>
|
||||
</column>
|
||||
<column id="122" parent="18" name="name">
|
||||
<column id="123" parent="18" name="name">
|
||||
<Position>1</Position>
|
||||
</column>
|
||||
<column id="123" parent="18" name="seq">
|
||||
<column id="124" parent="18" name="seq">
|
||||
<Position>2</Position>
|
||||
</column>
|
||||
<column id="124" parent="19" name="id">
|
||||
<column id="125" parent="19" name="id">
|
||||
<Position>1</Position>
|
||||
<DataType>INTEGER|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
<SequenceIdentity>1</SequenceIdentity>
|
||||
</column>
|
||||
<column id="125" parent="19" name="entityName">
|
||||
<column id="126" parent="19" name="entityName">
|
||||
<Position>2</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="126" parent="19" name="entityId">
|
||||
<column id="127" parent="19" name="entityId">
|
||||
<Position>3</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="127" parent="19" name="sourceId">
|
||||
<column id="128" parent="19" name="sourceId">
|
||||
<Position>4</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<column id="128" parent="19" name="syncDate">
|
||||
<column id="129" parent="19" name="syncDate">
|
||||
<Position>5</Position>
|
||||
<DataType>TEXT|0s</DataType>
|
||||
<NotNull>1</NotNull>
|
||||
</column>
|
||||
<index id="129" parent="19" name="IDX_sync_entityName_entityId">
|
||||
<index id="130" parent="19" name="IDX_sync_entityName_entityId">
|
||||
<ColNames>entityName
|
||||
entityId</ColNames>
|
||||
<ColumnCollations>
|
||||
</ColumnCollations>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
<Unique>1</Unique>
|
||||
</index>
|
||||
<index id="130" parent="19" name="IDX_sync_syncDate">
|
||||
<index id="131" parent="19" name="IDX_sync_syncDate">
|
||||
<ColNames>syncDate</ColNames>
|
||||
<ColumnCollations></ColumnCollations>
|
||||
</index>
|
||||
<key id="131" parent="19">
|
||||
<key id="132" parent="19">
|
||||
<ColNames>id</ColNames>
|
||||
<Primary>1</Primary>
|
||||
</key>
|
||||
|
||||
@@ -24,9 +24,9 @@ jq '.version = "'$VERSION'"' package.json|sponge package.json
|
||||
|
||||
git add package.json
|
||||
|
||||
echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > services/build.js
|
||||
echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > src/services/build.js
|
||||
|
||||
git add services/build.js
|
||||
git add src/services/build.js
|
||||
|
||||
TAG=v$VERSION
|
||||
|
||||
|
||||
5
db/migrations/0087__add_type_mime_to_note_revision.sql
Normal file
5
db/migrations/0087__add_type_mime_to_note_revision.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE note_revisions ADD type TEXT DEFAULT '' NOT NULL;
|
||||
ALTER TABLE note_revisions ADD mime TEXT DEFAULT '' NOT NULL;
|
||||
|
||||
UPDATE note_revisions SET type = (SELECT type FROM notes WHERE notes.noteId = note_revisions.noteId);
|
||||
UPDATE note_revisions SET mime = (SELECT mime FROM notes WHERE notes.noteId = note_revisions.noteId);
|
||||
34
db/migrations/0088__non_null_note_title_content.sql
Normal file
34
db/migrations/0088__non_null_note_title_content.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
CREATE TABLE event_logc027
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
noteId TEXT,
|
||||
comment TEXT,
|
||||
dateAdded TEXT NOT NULL
|
||||
);
|
||||
INSERT INTO event_logc027(id, noteId, comment, dateAdded) SELECT id, noteId, comment, dateAdded FROM event_log;
|
||||
DROP TABLE event_log;
|
||||
ALTER TABLE event_logc027 RENAME TO event_log;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "notes_mig" (
|
||||
`noteId` TEXT NOT NULL,
|
||||
`title` TEXT NOT NULL DEFAULT "unnamed",
|
||||
`content` TEXT NOT NULL DEFAULT "",
|
||||
`isProtected` INT NOT NULL DEFAULT 0,
|
||||
`isDeleted` INT NOT NULL DEFAULT 0,
|
||||
`dateCreated` TEXT NOT NULL,
|
||||
`dateModified` TEXT NOT NULL,
|
||||
type TEXT NOT NULL DEFAULT 'text',
|
||||
mime TEXT NOT NULL DEFAULT 'text/html',
|
||||
PRIMARY KEY(`noteId`)
|
||||
);
|
||||
|
||||
INSERT INTO notes_mig (noteId, title, content, isProtected, isDeleted, dateCreated, dateModified, type, mime)
|
||||
SELECT noteId, title, content, isProtected, isDeleted, dateCreated, dateModified, type, mime FROM notes;
|
||||
|
||||
DROP TABLE notes;
|
||||
|
||||
ALTER TABLE notes_mig RENAME TO notes;
|
||||
|
||||
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (
|
||||
`isDeleted`
|
||||
);
|
||||
@@ -21,28 +21,6 @@ CREATE TABLE IF NOT EXISTS "source_ids" (
|
||||
`dateCreated` TEXT NOT NULL,
|
||||
PRIMARY KEY(`sourceId`)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "notes" (
|
||||
`noteId` TEXT NOT NULL,
|
||||
`title` TEXT,
|
||||
`content` TEXT,
|
||||
`isProtected` INT NOT NULL DEFAULT 0,
|
||||
`isDeleted` INT NOT NULL DEFAULT 0,
|
||||
`dateCreated` TEXT NOT NULL,
|
||||
`dateModified` TEXT NOT NULL,
|
||||
type TEXT NOT NULL DEFAULT 'text',
|
||||
mime TEXT NOT NULL DEFAULT 'text/html',
|
||||
PRIMARY KEY(`noteId`)
|
||||
);
|
||||
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (
|
||||
`isDeleted`
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "event_log" (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`noteId` TEXT,
|
||||
`comment` TEXT,
|
||||
`dateAdded` TEXT NOT NULL,
|
||||
FOREIGN KEY(noteId) REFERENCES notes(noteId)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "note_revisions" (
|
||||
`noteRevisionId` TEXT NOT NULL PRIMARY KEY,
|
||||
`noteId` TEXT NOT NULL,
|
||||
@@ -51,7 +29,7 @@ CREATE TABLE IF NOT EXISTS "note_revisions" (
|
||||
`isProtected` INT NOT NULL DEFAULT 0,
|
||||
`dateModifiedFrom` TEXT NOT NULL,
|
||||
`dateModifiedTo` TEXT NOT NULL
|
||||
);
|
||||
, type TEXT DEFAULT '' NOT NULL, mime TEXT DEFAULT '' NOT NULL);
|
||||
CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` (
|
||||
`noteId`
|
||||
);
|
||||
@@ -130,3 +108,25 @@ CREATE INDEX IDX_labels_name_value
|
||||
on labels (name, value);
|
||||
CREATE INDEX IDX_labels_noteId
|
||||
on labels (noteId);
|
||||
CREATE TABLE IF NOT EXISTS "event_log"
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
noteId TEXT,
|
||||
comment TEXT,
|
||||
dateAdded TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "notes" (
|
||||
`noteId` TEXT NOT NULL,
|
||||
`title` TEXT NOT NULL DEFAULT "unnamed",
|
||||
`content` TEXT NOT NULL DEFAULT "",
|
||||
`isProtected` INT NOT NULL DEFAULT 0,
|
||||
`isDeleted` INT NOT NULL DEFAULT 0,
|
||||
`dateCreated` TEXT NOT NULL,
|
||||
`dateModified` TEXT NOT NULL,
|
||||
type TEXT NOT NULL DEFAULT 'text',
|
||||
mime TEXT NOT NULL DEFAULT 'text/html',
|
||||
PRIMARY KEY(`noteId`)
|
||||
);
|
||||
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (
|
||||
`isDeleted`
|
||||
);
|
||||
|
||||
@@ -76,12 +76,12 @@ app.on('ready', () => {
|
||||
const dateNoteService = require('./src/services/date_notes');
|
||||
const dateUtils = require('./src/services/date_utils');
|
||||
|
||||
const parentNoteId = await dateNoteService.getDateNoteId(dateUtils.nowDate());
|
||||
const parentNote = await dateNoteService.getDateNote(dateUtils.nowDate());
|
||||
|
||||
// window may be hidden / not in focus
|
||||
mainWindow.focus();
|
||||
|
||||
mainWindow.webContents.send('create-day-sub-note', parentNoteId);
|
||||
mainWindow.webContents.send('create-day-sub-note', parentNote.noteId);
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.10.0-beta",
|
||||
"version": "0.12.0",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"repository": {
|
||||
|
||||
@@ -12,13 +12,21 @@ class Note extends Entity {
|
||||
constructor(row) {
|
||||
super(row);
|
||||
|
||||
if (this.isProtected) {
|
||||
// check if there's noteId, otherwise this is a new entity which wasn't encrypted yet
|
||||
if (this.isProtected && this.noteId) {
|
||||
protected_session.decryptNote(this);
|
||||
}
|
||||
|
||||
if (this.isJson()) {
|
||||
this.setContent(this.content);
|
||||
}
|
||||
|
||||
setContent(content) {
|
||||
this.content = content;
|
||||
|
||||
try {
|
||||
this.jsonContent = JSON.parse(this.content);
|
||||
}
|
||||
catch(e) {}
|
||||
}
|
||||
|
||||
isJson() {
|
||||
@@ -133,7 +141,7 @@ class Note extends Entity {
|
||||
beforeSaving() {
|
||||
super.beforeSaving();
|
||||
|
||||
if (this.isJson()) {
|
||||
if (this.isJson() && this.jsonContent) {
|
||||
this.content = JSON.stringify(this.jsonContent, null, '\t');
|
||||
}
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@ async function showDialog() {
|
||||
$clonePrefix.val('');
|
||||
$linkTitle.val('');
|
||||
|
||||
function setDefaultLinkTitle(noteId) {
|
||||
const noteTitle = treeUtils.getNoteTitle(noteId);
|
||||
async function setDefaultLinkTitle(noteId) {
|
||||
const noteTitle = await treeUtils.getNoteTitle(noteId);
|
||||
|
||||
$linkTitle.val(noteTitle);
|
||||
}
|
||||
@@ -54,7 +54,7 @@ async function showDialog() {
|
||||
$autoComplete.autocomplete({
|
||||
source: await autocompleteService.getAutocompleteItems(),
|
||||
minLength: 0,
|
||||
change: () => {
|
||||
change: async () => {
|
||||
const val = $autoComplete.val();
|
||||
const notePath = linkService.getNodePathFromLabel(val);
|
||||
if (!notePath) {
|
||||
@@ -64,16 +64,16 @@ async function showDialog() {
|
||||
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
||||
|
||||
if (noteId) {
|
||||
setDefaultLinkTitle(noteId);
|
||||
await setDefaultLinkTitle(noteId);
|
||||
}
|
||||
},
|
||||
// this is called when user goes through autocomplete list with keyboard
|
||||
// at this point the item isn't selected yet so we use supplied ui.item to see WHERE the cursor is
|
||||
focus: (event, ui) => {
|
||||
focus: async (event, ui) => {
|
||||
const notePath = linkService.getNodePathFromLabel(ui.item.value);
|
||||
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
||||
|
||||
setDefaultLinkTitle(noteId);
|
||||
await setDefaultLinkTitle(noteId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,7 +54,13 @@ $list.on('change', () => {
|
||||
const revisionItem = revisionItems.find(r => r.noteRevisionId === optVal);
|
||||
|
||||
$title.html(revisionItem.title);
|
||||
$content.html(revisionItem.content);
|
||||
|
||||
if (revisionItem.type === 'text') {
|
||||
$content.html(revisionItem.content);
|
||||
}
|
||||
else if (revisionItem.type === 'code') {
|
||||
$content.html($("<pre>").text(revisionItem.content));
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', "a[action='note-revision']", event => {
|
||||
|
||||
@@ -14,6 +14,10 @@ class Branch {
|
||||
return await this.treeCache.getNote(this.noteId);
|
||||
}
|
||||
|
||||
isTopLevel() {
|
||||
return this.parentNoteId === 'root';
|
||||
}
|
||||
|
||||
get toString() {
|
||||
return `Branch(branchId=${this.branchId})`;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,11 @@ class NoteFull extends NoteShort {
|
||||
|
||||
this.content = row.content;
|
||||
|
||||
if (this.isJson()) {
|
||||
this.jsonContent = JSON.parse(this.content);
|
||||
if (this.content !== "" && this.isJson()) {
|
||||
try {
|
||||
this.jsonContent = JSON.parse(this.content);
|
||||
}
|
||||
catch(e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ class NoteShort {
|
||||
}
|
||||
|
||||
async getChildBranches() {
|
||||
if (!this.treeCache.children[this.noteId]) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const branches = [];
|
||||
|
||||
for (const child of this.treeCache.children[this.noteId]) {
|
||||
@@ -44,6 +48,14 @@ class NoteShort {
|
||||
get toString() {
|
||||
return `Note(noteId=${this.noteId}, title=${this.title})`;
|
||||
}
|
||||
|
||||
get dto() {
|
||||
const dto = Object.assign({}, this);
|
||||
delete dto.treeCache;
|
||||
delete dto.hideInAutocomplete;
|
||||
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
|
||||
export default NoteShort;
|
||||
@@ -1,20 +1,19 @@
|
||||
import server from './services/server.js';
|
||||
|
||||
$(document).ready(() => {
|
||||
server.get('migration').then(result => {
|
||||
const appDbVersion = result.app_dbVersion;
|
||||
const dbVersion = result.dbVersion;
|
||||
$(document).ready(async () => {
|
||||
const {appDbVersion, dbVersion} = await server.get('migration');
|
||||
|
||||
if (appDbVersion === dbVersion) {
|
||||
$("#up-to-date").show();
|
||||
}
|
||||
else {
|
||||
$("#need-to-migrate").show();
|
||||
console.log("HI", {appDbVersion, dbVersion});
|
||||
|
||||
$("#app-db-version").html(appDbVersion);
|
||||
$("#db-version").html(dbVersion);
|
||||
}
|
||||
});
|
||||
if (appDbVersion === dbVersion) {
|
||||
$("#up-to-date").show();
|
||||
}
|
||||
else {
|
||||
$("#need-to-migrate").show();
|
||||
|
||||
$("#app-db-version").html(appDbVersion);
|
||||
$("#db-version").html(dbVersion);
|
||||
}
|
||||
});
|
||||
|
||||
$("#run-migration").click(async () => {
|
||||
@@ -37,4 +36,11 @@ $("#run-migration").click(async () => {
|
||||
|
||||
$("#migration-table").append(row);
|
||||
}
|
||||
});
|
||||
|
||||
// copy of this shortcut to be able to debug migration problems
|
||||
$(document).bind('keydown', 'ctrl+shift+i', () => {
|
||||
require('electron').remote.getCurrentWindow().toggleDevTools();
|
||||
|
||||
return false;
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import treeCache from "./tree_cache.js";
|
||||
import treeUtils from "./tree_utils.js";
|
||||
import protectedSessionHolder from './protected_session_holder.js';
|
||||
|
||||
async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
|
||||
if (!parentNoteId) {
|
||||
@@ -21,9 +22,6 @@ async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
|
||||
titlePath = '';
|
||||
}
|
||||
|
||||
// https://github.com/zadam/trilium/issues/46
|
||||
// unfortunately not easy to implement because we don't have an easy access to note's isProtected property
|
||||
|
||||
const autocompleteItems = [];
|
||||
|
||||
for (const childNote of childNotes) {
|
||||
@@ -34,10 +32,12 @@ async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
|
||||
const childNotePath = (notePath ? (notePath + '/') : '') + childNote.noteId;
|
||||
const childTitlePath = (titlePath ? (titlePath + ' / ') : '') + await treeUtils.getNoteTitle(childNote.noteId, parentNoteId);
|
||||
|
||||
autocompleteItems.push({
|
||||
value: childTitlePath + ' (' + childNotePath + ')',
|
||||
label: childTitlePath
|
||||
});
|
||||
if (!childNote.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||
autocompleteItems.push({
|
||||
value: childTitlePath + ' (' + childNotePath + ')',
|
||||
label: childTitlePath
|
||||
});
|
||||
}
|
||||
|
||||
const childItems = await getAutocompleteItems(childNote.noteId, childNotePath, childTitlePath);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import treeUtils from './tree_utils.js';
|
||||
import branchPrefixDialog from '../dialogs/branch_prefix.js';
|
||||
import infoService from "./info.js";
|
||||
import treeCache from "./tree_cache.js";
|
||||
import syncService from "./sync.js";
|
||||
|
||||
const $tree = $("#tree");
|
||||
|
||||
@@ -103,7 +104,7 @@ const contextMenuOptions = {
|
||||
],
|
||||
beforeOpen: async (event, ui) => {
|
||||
const node = $.ui.fancytree.getNode(ui.target);
|
||||
const branch = await treeCache.getBranch(branchId);
|
||||
const branch = await treeCache.getBranch(node.data.branchId);
|
||||
const note = await treeCache.getNote(node.data.noteId);
|
||||
const parentNote = await treeCache.getNote(branch.parentNoteId);
|
||||
|
||||
|
||||
@@ -32,18 +32,19 @@ async function requireLibrary(library) {
|
||||
}
|
||||
}
|
||||
|
||||
const dynamicallyLoadedScripts = [];
|
||||
// we save the promises in case of the same script being required concurrently multiple times
|
||||
const loadedScriptPromises = {};
|
||||
|
||||
async function requireScript(url) {
|
||||
if (!dynamicallyLoadedScripts.includes(url)) {
|
||||
dynamicallyLoadedScripts.push(url);
|
||||
|
||||
return await $.ajax({
|
||||
if (!loadedScriptPromises[url]) {
|
||||
loadedScriptPromises[url] = $.ajax({
|
||||
url: url,
|
||||
dataType: "script",
|
||||
cache: true
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
await loadedScriptPromises[url];
|
||||
}
|
||||
|
||||
async function requireCss(url) {
|
||||
|
||||
@@ -76,9 +76,11 @@ function goToLink(e) {
|
||||
|
||||
function addLinkToEditor(linkTitle, linkHref) {
|
||||
const editor = noteDetailText.getEditor();
|
||||
const doc = editor.document;
|
||||
|
||||
doc.enqueueChanges(() => editor.data.insertLink(linkTitle, linkHref), doc.selection);
|
||||
editor.model.change( writer => {
|
||||
const insertPosition = editor.model.document.selection.getFirstPosition();
|
||||
writer.insertText(linkTitle, { linkHref: linkHref }, insertPosition);
|
||||
});
|
||||
}
|
||||
|
||||
function addTextToEditor(text) {
|
||||
|
||||
@@ -100,7 +100,7 @@ setTimeout(() => {
|
||||
lastSyncId: lastSyncId
|
||||
}));
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
}, 0);
|
||||
|
||||
export default {
|
||||
logError,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import treeService from './tree.js';
|
||||
import treeUtils from './tree_utils.js';
|
||||
import noteTypeService from './note_type.js';
|
||||
import protectedSessionService from './protected_session.js';
|
||||
import protectedSessionHolder from './protected_session_holder.js';
|
||||
@@ -24,6 +25,7 @@ const $noteDetailWrapper = $("#note-detail-wrapper");
|
||||
const $noteIdDisplay = $("#note-id-display");
|
||||
const $labelList = $("#label-list");
|
||||
const $labelListInner = $("#label-list-inner");
|
||||
const $childrenOverview = $("#children-overview");
|
||||
|
||||
let currentNote = null;
|
||||
|
||||
@@ -73,50 +75,42 @@ function noteChanged() {
|
||||
async function reload() {
|
||||
// no saving here
|
||||
|
||||
await loadNoteToEditor(getCurrentNoteId());
|
||||
await loadNoteDetail(getCurrentNoteId());
|
||||
}
|
||||
|
||||
async function switchToNote(noteId) {
|
||||
if (getCurrentNoteId() !== noteId) {
|
||||
await saveNoteIfChanged();
|
||||
|
||||
await loadNoteToEditor(noteId);
|
||||
await loadNoteDetail(noteId);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveNote() {
|
||||
const note = getCurrentNote();
|
||||
|
||||
note.title = $noteTitle.val();
|
||||
note.content = getComponent(note.type).getContent();
|
||||
|
||||
treeService.setNoteTitle(note.noteId, note.title);
|
||||
|
||||
await server.put('notes/' + note.noteId, note.dto);
|
||||
|
||||
isNoteChanged = false;
|
||||
|
||||
if (note.isProtected) {
|
||||
protectedSessionHolder.touchProtectedSession();
|
||||
}
|
||||
|
||||
infoService.showMessage("Saved!");
|
||||
}
|
||||
|
||||
async function saveNoteIfChanged() {
|
||||
if (!isNoteChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
const note = getCurrentNote();
|
||||
|
||||
updateNoteFromInputs(note);
|
||||
|
||||
await saveNoteToServer(note);
|
||||
|
||||
if (note.isProtected) {
|
||||
protectedSessionHolder.touchProtectedSession();
|
||||
}
|
||||
}
|
||||
|
||||
function updateNoteFromInputs(note) {
|
||||
note.title = $noteTitle.val();
|
||||
note.content = getComponent(note.type).getContent();
|
||||
|
||||
treeService.setNoteTitle(note.noteId, note.title);
|
||||
}
|
||||
|
||||
async function saveNoteToServer(note) {
|
||||
const dto = Object.assign({}, note);
|
||||
delete dto.treeCache;
|
||||
delete dto.hideInAutocomplete;
|
||||
|
||||
await server.put('notes/' + dto.noteId, dto);
|
||||
|
||||
isNoteChanged = false;
|
||||
|
||||
infoService.showMessage("Saved!");
|
||||
await saveNote();
|
||||
}
|
||||
|
||||
function setNoteBackgroundIfProtected(note) {
|
||||
@@ -145,7 +139,7 @@ async function handleProtectedSession() {
|
||||
protectedSessionService.ensureDialogIsClosed();
|
||||
}
|
||||
|
||||
async function loadNoteToEditor(noteId) {
|
||||
async function loadNoteDetail(noteId) {
|
||||
currentNote = await loadNote(noteId);
|
||||
|
||||
if (isNewNoteCreated) {
|
||||
@@ -182,7 +176,35 @@ async function loadNoteToEditor(noteId) {
|
||||
// after loading new note make sure editor is scrolled to the top
|
||||
$noteDetailWrapper.scrollTop(0);
|
||||
|
||||
await loadLabelList();
|
||||
const labels = await loadLabelList();
|
||||
|
||||
const hideChildrenOverview = labels.some(label => label.name === 'hideChildrenOverview');
|
||||
await showChildrenOverview(hideChildrenOverview);
|
||||
}
|
||||
|
||||
async function showChildrenOverview(hideChildrenOverview) {
|
||||
if (hideChildrenOverview) {
|
||||
$childrenOverview.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const note = getCurrentNote();
|
||||
|
||||
$childrenOverview.empty();
|
||||
|
||||
const notePath = treeService.getCurrentNotePath();
|
||||
|
||||
for (const childBranch of await note.getChildBranches()) {
|
||||
const link = $('<a>', {
|
||||
href: 'javascript:',
|
||||
text: await treeUtils.getNoteTitle(childBranch.noteId, childBranch.parentNoteId)
|
||||
}).attr('action', 'note').attr('note-path', notePath + '/' + childBranch.noteId);
|
||||
|
||||
const childEl = $('<div class="child-overview">').html(link);
|
||||
$childrenOverview.append(childEl);
|
||||
}
|
||||
|
||||
$childrenOverview.show();
|
||||
}
|
||||
|
||||
async function loadLabelList() {
|
||||
@@ -202,6 +224,8 @@ async function loadLabelList() {
|
||||
else {
|
||||
$labelList.hide();
|
||||
}
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
async function loadNote(noteId) {
|
||||
@@ -245,8 +269,6 @@ setInterval(saveNoteIfChanged, 5000);
|
||||
export default {
|
||||
reload,
|
||||
switchToNote,
|
||||
updateNoteFromInputs,
|
||||
saveNoteToServer,
|
||||
setNoteBackgroundIfProtected,
|
||||
loadNote,
|
||||
getCurrentNote,
|
||||
@@ -255,6 +277,7 @@ export default {
|
||||
newNoteCreated,
|
||||
focus,
|
||||
loadLabelList,
|
||||
saveNote,
|
||||
saveNoteIfChanged,
|
||||
noteChanged
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import utils from "./utils.js";
|
||||
import libraryLoader from "./library_loader.js";
|
||||
import bundleService from "./bundle.js";
|
||||
import infoService from "./info.js";
|
||||
@@ -11,15 +10,19 @@ const $noteDetailCode = $('#note-detail-code');
|
||||
const $executeScriptButton = $("#execute-script-button");
|
||||
|
||||
async function show() {
|
||||
if (!codeEditor) {
|
||||
await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR);
|
||||
await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR);
|
||||
|
||||
if (!codeEditor) {
|
||||
CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
|
||||
CodeMirror.keyMap.default["Tab"] = "indentMore";
|
||||
|
||||
// these conflict with backward/forward navigation shortcuts
|
||||
delete CodeMirror.keyMap.default["Alt-Left"];
|
||||
delete CodeMirror.keyMap.default["Alt-Right"];
|
||||
|
||||
CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js';
|
||||
|
||||
codeEditor = CodeMirror($("#note-detail-code")[0], {
|
||||
codeEditor = CodeMirror($noteDetailCode[0], {
|
||||
value: "",
|
||||
viewportMargin: Infinity,
|
||||
indentUnit: 4,
|
||||
@@ -28,7 +31,8 @@ async function show() {
|
||||
highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false},
|
||||
lint: true,
|
||||
gutters: ["CodeMirror-lint-markers"],
|
||||
lineNumbers: true
|
||||
lineNumbers: true,
|
||||
tabindex: 2 // so that tab from title will lead to code editor focus
|
||||
});
|
||||
|
||||
codeEditor.on('change', noteDetailService.noteChanged);
|
||||
@@ -38,7 +42,7 @@ async function show() {
|
||||
|
||||
const currentNote = noteDetailService.getCurrentNote();
|
||||
|
||||
// this needs to happen after the element is shown, otherwise the editor won't be refresheds
|
||||
// this needs to happen after the element is shown, otherwise the editor won't be refreshed
|
||||
codeEditor.setValue(currentNote.content);
|
||||
|
||||
const info = CodeMirror.findModeByMIME(currentNote.mime);
|
||||
@@ -67,13 +71,13 @@ async function executeCurrentNote() {
|
||||
const currentNote = noteDetailService.getCurrentNote();
|
||||
|
||||
if (currentNote.mime.endsWith("env=frontend")) {
|
||||
const bundle = await server.get('script/bundle/' + getCurrentNoteId());
|
||||
const bundle = await server.get('script/bundle/' + noteDetailService.getCurrentNoteId());
|
||||
|
||||
bundleService.executeBundle(bundle);
|
||||
}
|
||||
|
||||
if (currentNote.mime.endsWith("env=backend")) {
|
||||
await server.post('script/run/' + getCurrentNoteId());
|
||||
await server.post('script/run/' + noteDetailService.getCurrentNoteId());
|
||||
}
|
||||
|
||||
infoService.showMessage("Note executed");
|
||||
|
||||
@@ -11,11 +11,10 @@ async function show() {
|
||||
|
||||
textEditor = await BalloonEditor.create($noteDetailText[0], {});
|
||||
|
||||
textEditor.document.on('change', noteDetailService.noteChanged);
|
||||
textEditor.model.document.on('change', noteDetailService.noteChanged);
|
||||
}
|
||||
|
||||
// temporary workaround for https://github.com/ckeditor/ckeditor5-enter/issues/49
|
||||
textEditor.setData(noteDetailService.getCurrentNote().content || "<p></p>");
|
||||
textEditor.setData(noteDetailService.getCurrentNote().content);
|
||||
|
||||
$noteDetailText.show();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import treeService from './tree.js';
|
||||
import noteDetail from './note_detail.js';
|
||||
import noteDetailService from './note_detail.js';
|
||||
import server from './server.js';
|
||||
import infoService from "./info.js";
|
||||
|
||||
@@ -84,13 +84,13 @@ function NoteTypeModel() {
|
||||
};
|
||||
|
||||
async function save() {
|
||||
const note = noteDetail.getCurrentNote();
|
||||
const note = noteDetailService.getCurrentNote();
|
||||
|
||||
await server.put('notes/' + note.noteId
|
||||
+ '/type/' + encodeURIComponent(self.type())
|
||||
+ '/mime/' + encodeURIComponent(self.mime()));
|
||||
|
||||
await noteDetail.reload();
|
||||
await noteDetailService.reload();
|
||||
|
||||
// for the note icon to be updated in the tree
|
||||
await treeService.reload();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import treeService from './tree.js';
|
||||
import noteDetail from './note_detail.js';
|
||||
import noteDetailService from './note_detail.js';
|
||||
import utils from './utils.js';
|
||||
import server from './server.js';
|
||||
import protectedSessionHolder from './protected_session_holder.js';
|
||||
@@ -57,7 +57,7 @@ async function setupProtectedSession() {
|
||||
|
||||
$dialog.dialog("close");
|
||||
|
||||
noteDetail.reload();
|
||||
noteDetailService.reload();
|
||||
treeService.reload();
|
||||
|
||||
if (protectedSessionDeferred !== null) {
|
||||
@@ -90,33 +90,27 @@ async function enterProtectedSession(password) {
|
||||
async function protectNoteAndSendToServer() {
|
||||
await ensureProtectedSession(true, true);
|
||||
|
||||
const note = noteDetail.getCurrentNote();
|
||||
|
||||
noteDetail.updateNoteFromInputs(note);
|
||||
|
||||
const note = noteDetailService.getCurrentNote();
|
||||
note.isProtected = true;
|
||||
|
||||
await noteDetail.saveNoteToServer(note);
|
||||
await noteDetailService.saveNote(note);
|
||||
|
||||
treeService.setProtected(note.noteId, note.isProtected);
|
||||
|
||||
noteDetail.setNoteBackgroundIfProtected(note);
|
||||
noteDetailService.setNoteBackgroundIfProtected(note);
|
||||
}
|
||||
|
||||
async function unprotectNoteAndSendToServer() {
|
||||
await ensureProtectedSession(true, true);
|
||||
|
||||
const note = noteDetail.getCurrentNote();
|
||||
|
||||
noteDetail.updateNoteFromInputs(note);
|
||||
|
||||
const note = noteDetailService.getCurrentNote();
|
||||
note.isProtected = false;
|
||||
|
||||
await noteDetail.saveNoteToServer(note);
|
||||
await noteDetailService.saveNote(note);
|
||||
|
||||
treeService.setProtected(note.noteId, note.isProtected);
|
||||
|
||||
noteDetail.setNoteBackgroundIfProtected(note);
|
||||
noteDetailService.setNoteBackgroundIfProtected(note);
|
||||
}
|
||||
|
||||
async function protectBranch(noteId, protect) {
|
||||
@@ -127,7 +121,7 @@ async function protectBranch(noteId, protect) {
|
||||
infoService.showMessage("Request to un/protect sub tree has finished successfully");
|
||||
|
||||
treeService.reload();
|
||||
noteDetail.reload();
|
||||
noteDetailService.reload();
|
||||
}
|
||||
|
||||
$passwordForm.submit(() => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import treeService from './tree.js';
|
||||
import server from './server.js';
|
||||
import utils from './utils.js';
|
||||
import infoService from './info.js';
|
||||
|
||||
function ScriptApi(startNote, currentNote) {
|
||||
const $pluginButtons = $("#plugin-buttons");
|
||||
@@ -54,7 +55,11 @@ function ScriptApi(startNote, currentNote) {
|
||||
activateNote,
|
||||
getInstanceName: () => window.glob.instanceName,
|
||||
runOnServer,
|
||||
formatDateISO: utils.formatDateISO
|
||||
formatDateISO: utils.formatDateISO,
|
||||
parseDate: utils.parseDate,
|
||||
showMessage: infoService.showMessage,
|
||||
showError: infoService.showError,
|
||||
reloadTree: treeService.reload
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,19 +85,17 @@ async function ajax(url, method, data) {
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (utils.isElectron()) {
|
||||
const ipc = require('electron').ipcRenderer;
|
||||
if (utils.isElectron()) {
|
||||
const ipc = require('electron').ipcRenderer;
|
||||
|
||||
ipc.on('server-response', (event, arg) => {
|
||||
console.log(utils.now(), "Response #" + arg.requestId + ": " + arg.statusCode);
|
||||
ipc.on('server-response', (event, arg) => {
|
||||
console.log(utils.now(), "Response #" + arg.requestId + ": " + arg.statusCode);
|
||||
|
||||
reqResolves[arg.requestId](arg.body);
|
||||
reqResolves[arg.requestId](arg.body);
|
||||
|
||||
delete reqResolves[arg.requestId];
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
delete reqResolves[arg.requestId];
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
get,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import utils from './utils.js';
|
||||
import server from './server.js';
|
||||
import infoService from "./info.js";
|
||||
|
||||
async function syncNow() {
|
||||
@@ -19,7 +19,7 @@ async function syncNow() {
|
||||
$("#sync-now-button").click(syncNow);
|
||||
|
||||
async function forceNoteSync(noteId) {
|
||||
const result = await server.post('sync/force-note-sync/' + noteId);
|
||||
await server.post('sync/force-note-sync/' + noteId);
|
||||
|
||||
infoService.showMessage("Note added to sync queue.");
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ function initFancyTree(branch) {
|
||||
keyboard: false, // we takover keyboard handling in the hotkeys plugin
|
||||
extensions: ["hotkeys", "filter", "dnd", "clones"],
|
||||
source: branch,
|
||||
scrollParent: $("#tree"),
|
||||
scrollParent: $tree,
|
||||
click: (event, data) => {
|
||||
const targetType = data.targetType;
|
||||
const node = data.node;
|
||||
@@ -541,9 +541,9 @@ $(window).bind('hashchange', function() {
|
||||
});
|
||||
|
||||
utils.bindShortcut('alt+c', () => collapseTree()); // don't use shortened form since collapseTree() accepts argument
|
||||
$collapseTreeButton.click(() => collapseTree());
|
||||
|
||||
$createTopLevelNoteButton.click(createNewTopLevelNote);
|
||||
$collapseTreeButton.click(collapseTree);
|
||||
$scrollToCurrentNoteButton.click(scrollToCurrentNote);
|
||||
|
||||
export default {
|
||||
|
||||
2
src/public/libraries/ckeditor/ckeditor.js
vendored
2
src/public/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
Binary file not shown.
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
@@ -12,9 +12,40 @@
|
||||
/*------------------------------------------------------------------------------
|
||||
* Helpers
|
||||
*----------------------------------------------------------------------------*/
|
||||
.ui-helper-hidden {
|
||||
.fancytree-helper-hidden {
|
||||
display: none;
|
||||
}
|
||||
.fancytree-helper-indeterminate-cb {
|
||||
color: #777;
|
||||
}
|
||||
.fancytree-helper-disabled {
|
||||
color: #c0c0c0;
|
||||
}
|
||||
/* Helper to allow spinning loader icon with glyph-, ligature-, and SVG-icons. */
|
||||
.fancytree-helper-spin {
|
||||
-webkit-animation: spin 1000ms infinite linear;
|
||||
animation: spin 1000ms infinite linear;
|
||||
}
|
||||
@-webkit-keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(359deg);
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
@keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(359deg);
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
/*------------------------------------------------------------------------------
|
||||
* Container and UL / LI
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -338,6 +369,16 @@ span.fancytree-node.fancytree-error span.fancytree-title {
|
||||
/*------------------------------------------------------------------------------
|
||||
* Drag'n'drop support
|
||||
*----------------------------------------------------------------------------*/
|
||||
/* ext-dnd5: */
|
||||
span.fancytree-childcounter {
|
||||
color: #fff;
|
||||
background: #337ab7;
|
||||
border: 1px solid gray;
|
||||
border-radius: 10px;
|
||||
padding: 2px;
|
||||
text-align: center;
|
||||
}
|
||||
/* ext-dnd: */
|
||||
div.fancytree-drag-helper span.fancytree-childcounter,
|
||||
div.fancytree-drag-helper span.fancytree-dnd-modifier {
|
||||
display: inline-block;
|
||||
@@ -402,8 +443,7 @@ span.fancytree-drag-source.fancytree-drag-remove {
|
||||
.fancytree-container.fancytree-rtl span.fancytree-connector,
|
||||
.fancytree-container.fancytree-rtl span.fancytree-expander,
|
||||
.fancytree-container.fancytree-rtl span.fancytree-icon,
|
||||
.fancytree-container.fancytree-rtl span.fancytree-drag-helper-img,
|
||||
.fancytree-container.fancytree-rtl #fancytree-drop-marker {
|
||||
.fancytree-container.fancytree-rtl span.fancytree-drag-helper-img {
|
||||
background-image: url("../skin-win8/icons-rtl.gif");
|
||||
}
|
||||
.fancytree-container.fancytree-rtl .fancytree-exp-n span.fancytree-expander,
|
||||
@@ -425,6 +465,9 @@ ul.fancytree-container.fancytree-rtl li.fancytree-lastsib,
|
||||
ul.fancytree-container.fancytree-rtl.fancytree-no-connector > li {
|
||||
background-image: none;
|
||||
}
|
||||
#fancytree-drop-marker.fancytree-rtl {
|
||||
background-image: url("../skin-win8/icons-rtl.gif");
|
||||
}
|
||||
/*------------------------------------------------------------------------------
|
||||
* 'table' extension
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -482,7 +525,7 @@ table.fancytree-ext-columnview .fancytree-has-children span.fancytree-cv-right:h
|
||||
* 'filter' extension
|
||||
*----------------------------------------------------------------------------*/
|
||||
.fancytree-ext-filter-dimm span.fancytree-node span.fancytree-title {
|
||||
color: silver;
|
||||
color: #c0c0c0;
|
||||
font-weight: lighter;
|
||||
}
|
||||
.fancytree-ext-filter-dimm tr.fancytree-submatch span.fancytree-title,
|
||||
@@ -501,7 +544,7 @@ table.fancytree-ext-columnview .fancytree-has-children span.fancytree-cv-right:h
|
||||
}
|
||||
.fancytree-ext-filter-hide tr.fancytree-submatch span.fancytree-title,
|
||||
.fancytree-ext-filter-hide span.fancytree-node.fancytree-submatch span.fancytree-title {
|
||||
color: silver;
|
||||
color: #c0c0c0;
|
||||
font-weight: lighter;
|
||||
}
|
||||
.fancytree-ext-filter-hide tr.fancytree-match span.fancytree-title,
|
||||
|
||||
File diff suppressed because one or more lines are too long
799
src/public/libraries/jquery.js
vendored
799
src/public/libraries/jquery.js
vendored
File diff suppressed because it is too large
Load Diff
7
src/public/libraries/jquery.min.js
vendored
7
src/public/libraries/jquery.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4,18 +4,12 @@
|
||||
|
||||
display: grid;
|
||||
grid-template-areas: "header header"
|
||||
"tree-actions title"
|
||||
"search note-content"
|
||||
"tree note-content"
|
||||
"parent-list note-content"
|
||||
"parent-list label-list";
|
||||
"left-pane title"
|
||||
"left-pane note-detail";
|
||||
grid-template-columns: 2fr 5fr;
|
||||
grid-template-rows: auto
|
||||
auto
|
||||
auto
|
||||
1fr
|
||||
auto
|
||||
auto;
|
||||
1fr;
|
||||
|
||||
justify-content: center;
|
||||
grid-gap: 10px;
|
||||
@@ -28,6 +22,23 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#note-detail-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
grid-area: note-detail;
|
||||
padding-left: 10px;
|
||||
padding-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#note-detail-component-wrapper {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
flex-basis: content;
|
||||
}
|
||||
|
||||
.note-detail-component {
|
||||
display: none;
|
||||
}
|
||||
@@ -36,7 +47,7 @@
|
||||
border: 0 !important;
|
||||
box-shadow: none !important;
|
||||
/* This is because with empty content height of editor is 0 and it's impossible to click into it */
|
||||
min-height: 400px;
|
||||
min-height: 200px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@@ -249,7 +260,7 @@ div.ui-tooltip {
|
||||
}
|
||||
|
||||
#note-detail-code {
|
||||
height: 100%;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
@@ -274,7 +285,6 @@ div.ui-tooltip {
|
||||
.cm-matchhighlight {background-color: #eeeeee}
|
||||
|
||||
#label-list {
|
||||
grid-area: label-list;
|
||||
color: #777777;
|
||||
border-top: 1px solid #eee;
|
||||
padding: 5px; display: none;
|
||||
@@ -288,4 +298,36 @@ div.ui-tooltip {
|
||||
#file-table th, #file-table td {
|
||||
padding: 10px;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
#children-overview {
|
||||
flex-grow: 1000;
|
||||
flex-shrink: 1000;
|
||||
flex-basis: 0px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
height: 100px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.child-overview {
|
||||
font-weight: bold;
|
||||
font-size: large;
|
||||
padding: 10px;
|
||||
background: #f4f4f4;
|
||||
width: 150px;
|
||||
height: 90px;
|
||||
line-height: 2em;
|
||||
margin-right: 20px;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.child-overview a {
|
||||
color: #444;
|
||||
}
|
||||
@@ -13,7 +13,68 @@ async function exportNote(req, res) {
|
||||
|
||||
const pack = tar.pack();
|
||||
|
||||
const name = await exportNoteInner(branchId, '', pack);
|
||||
const exportedNoteIds = [];
|
||||
const name = await exportNoteInner(branchId, '');
|
||||
|
||||
async function exportNoteInner(branchId, directory) {
|
||||
const branch = await repository.getBranch(branchId);
|
||||
const note = await branch.getNote();
|
||||
const childFileName = directory + sanitize(note.title);
|
||||
|
||||
if (exportedNoteIds.includes(note.noteId)) {
|
||||
saveMetadataFile(childFileName, {
|
||||
version: 1,
|
||||
clone: true,
|
||||
noteId: note.noteId,
|
||||
prefix: branch.prefix
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const metadata = {
|
||||
version: 1,
|
||||
clone: false,
|
||||
noteId: note.noteId,
|
||||
title: note.title,
|
||||
prefix: branch.prefix,
|
||||
type: note.type,
|
||||
mime: note.mime,
|
||||
labels: (await note.getLabels()).map(label => {
|
||||
return {
|
||||
name: label.name,
|
||||
value: label.value
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
if (metadata.labels.find(label => label.name === 'excludeFromExport')) {
|
||||
return;
|
||||
}
|
||||
|
||||
saveMetadataFile(childFileName, metadata);
|
||||
saveDataFile(childFileName, note);
|
||||
|
||||
exportedNoteIds.push(note.noteId);
|
||||
|
||||
for (const child of await note.getChildBranches()) {
|
||||
await exportNoteInner(child.branchId, childFileName + "/");
|
||||
}
|
||||
|
||||
return childFileName;
|
||||
}
|
||||
|
||||
function saveDataFile(childFileName, note) {
|
||||
const content = note.type === 'text' ? html.prettyPrint(note.content, {indent_size: 2}) : note.content;
|
||||
|
||||
pack.entry({name: childFileName + ".dat", size: content.length}, content);
|
||||
}
|
||||
|
||||
function saveMetadataFile(childFileName, metadata) {
|
||||
const metadataJson = JSON.stringify(metadata, null, '\t');
|
||||
|
||||
pack.entry({name: childFileName + ".meta", size: metadataJson.length}, metadataJson);
|
||||
}
|
||||
|
||||
pack.finalize();
|
||||
|
||||
@@ -23,51 +84,6 @@ async function exportNote(req, res) {
|
||||
pack.pipe(res);
|
||||
}
|
||||
|
||||
async function exportNoteInner(branchId, directory, pack) {
|
||||
const branch = await repository.getBranch(branchId);
|
||||
const note = await branch.getNote();
|
||||
|
||||
if (note.isProtected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const metadata = await getMetadata(note);
|
||||
|
||||
if (metadata.labels.find(label => label.name === 'excludeFromExport')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const metadataJson = JSON.stringify(metadata, null, '\t');
|
||||
const childFileName = directory + sanitize(note.title);
|
||||
|
||||
pack.entry({ name: childFileName + ".meta", size: metadataJson.length }, metadataJson);
|
||||
|
||||
const content = note.type === 'text' ? html.prettyPrint(note.content, {indent_size: 2}) : note.content;
|
||||
|
||||
pack.entry({ name: childFileName + ".dat", size: content.length }, content);
|
||||
|
||||
for (const child of await note.getChildBranches()) {
|
||||
await exportNoteInner(child.branchId, childFileName + "/", pack);
|
||||
}
|
||||
|
||||
return childFileName;
|
||||
}
|
||||
|
||||
async function getMetadata(note) {
|
||||
return {
|
||||
version: 1,
|
||||
title: note.title,
|
||||
type: note.type,
|
||||
mime: note.mime,
|
||||
labels: (await note.getLabels()).map(label => {
|
||||
return {
|
||||
name: label.name,
|
||||
value: label.value
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
exportNote
|
||||
};
|
||||
@@ -3,6 +3,7 @@
|
||||
const repository = require('../../services/repository');
|
||||
const labelService = require('../../services/labels');
|
||||
const noteService = require('../../services/notes');
|
||||
const Branch = require('../../entities/branch');
|
||||
const tar = require('tar-stream');
|
||||
const stream = require('stream');
|
||||
const path = require('path');
|
||||
@@ -31,7 +32,7 @@ async function parseImportFile(file) {
|
||||
const extract = tar.extract();
|
||||
|
||||
extract.on('entry', function(header, stream, next) {
|
||||
let {name, key} = getFileName(header.name);
|
||||
const {name, key} = getFileName(header.name);
|
||||
|
||||
let file = fileMap[name];
|
||||
|
||||
@@ -97,30 +98,46 @@ async function importTar(req) {
|
||||
|
||||
const files = await parseImportFile(file);
|
||||
|
||||
await importNotes(files, parentNoteId);
|
||||
// maps from original noteId (in tar file) to newly generated noteId
|
||||
const noteIdMap = {};
|
||||
|
||||
await importNotes(files, parentNoteId, noteIdMap);
|
||||
}
|
||||
|
||||
async function importNotes(files, parentNoteId) {
|
||||
async function importNotes(files, parentNoteId, noteIdMap) {
|
||||
for (const file of files) {
|
||||
if (file.meta.version !== 1) {
|
||||
throw new Error("Can't read meta data version " + file.meta.version);
|
||||
}
|
||||
|
||||
if (file.meta.clone) {
|
||||
await new Branch({
|
||||
parentNoteId: parentNoteId,
|
||||
noteId: noteIdMap[file.meta.noteId],
|
||||
prefix: file.meta.prefix
|
||||
}).save();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.meta.type !== 'file') {
|
||||
file.data = file.data.toString("UTF-8");
|
||||
}
|
||||
|
||||
const {note} = await noteService.createNote(parentNoteId, file.meta.title, file.data, {
|
||||
type: file.meta.type,
|
||||
mime: file.meta.mime
|
||||
mime: file.meta.mime,
|
||||
prefix: file.meta.prefix
|
||||
});
|
||||
|
||||
noteIdMap[file.meta.noteId] = note.noteId;
|
||||
|
||||
for (const label of file.meta.labels) {
|
||||
await labelService.createLabel(note.noteId, label.name, label.value);
|
||||
}
|
||||
|
||||
if (file.children.length > 0) {
|
||||
await importNotes(file.children, note.noteId);
|
||||
await importNotes(file.children, note.noteId, noteIdMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ const appInfo = require('../../services/app_info');
|
||||
async function getMigrationInfo() {
|
||||
return {
|
||||
dbVersion: parseInt(await optionService.getOption('dbVersion')),
|
||||
app_dbVersion: appInfo.dbVersion
|
||||
appDbVersion: appInfo.dbVersion
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,9 @@ async function uploadImage(req) {
|
||||
return [400, "Unknown image type: " + file.mimetype];
|
||||
}
|
||||
|
||||
const parentNoteId = await dateNoteService.getDateNoteId(req.headers['x-local-date']);
|
||||
const parentNote = await dateNoteService.getDateNote(req.headers['x-local-date']);
|
||||
|
||||
const {note} = await noteService.createNewNote(parentNoteId, {
|
||||
const {note} = await noteService.createNewNote(parentNote.noteId, {
|
||||
title: "Sender image",
|
||||
content: "",
|
||||
target: 'into',
|
||||
@@ -57,9 +57,9 @@ async function uploadImage(req) {
|
||||
}
|
||||
|
||||
async function saveNote(req) {
|
||||
const parentNoteId = await dateNoteService.getDateNoteId(req.headers['x-local-date']);
|
||||
const parentNote = await dateNoteService.getDateNote(req.headers['x-local-date']);
|
||||
|
||||
await noteService.createNewNote(parentNoteId, {
|
||||
await noteService.createNewNote(parentNote.noteId, {
|
||||
title: req.body.title,
|
||||
content: req.body.content,
|
||||
target: 'into',
|
||||
|
||||
@@ -10,8 +10,8 @@ const log = require('../../services/log');
|
||||
|
||||
async function checkSync() {
|
||||
return {
|
||||
'hashes': await contentHashService.getHashes(),
|
||||
'max_sync_id': await sql.getValue('SELECT MAX(id) FROM sync')
|
||||
hashes: await contentHashService.getHashes(),
|
||||
maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync')
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,129 +55,21 @@ async function forceNoteSync(req) {
|
||||
syncService.sync();
|
||||
}
|
||||
|
||||
async function getChanged() {
|
||||
async function getChanged(req) {
|
||||
const lastSyncId = parseInt(req.query.lastSyncId);
|
||||
|
||||
return await sql.getRows("SELECT * FROM sync WHERE id > ?", [lastSyncId]);
|
||||
const syncs = await sql.getRows("SELECT * FROM sync WHERE id > ? LIMIT 1000", [lastSyncId]);
|
||||
|
||||
return await syncService.getSyncRecords(syncs);
|
||||
}
|
||||
|
||||
async function getNote(req) {
|
||||
const noteId = req.params.noteId;
|
||||
const entity = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
||||
async function update(req) {
|
||||
const sourceId = req.body.sourceId;
|
||||
const entities = req.body.entities;
|
||||
|
||||
syncService.serializeNoteContentBuffer(entity);
|
||||
|
||||
return {
|
||||
entity: entity
|
||||
};
|
||||
}
|
||||
|
||||
async function getBranch(req) {
|
||||
const branchId = req.params.branchId;
|
||||
|
||||
return await sql.getRow("SELECT * FROM branches WHERE branchId = ?", [branchId]);
|
||||
}
|
||||
|
||||
async function getNoteRevision(req) {
|
||||
const noteRevisionId = req.params.noteRevisionId;
|
||||
|
||||
return await sql.getRow("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [noteRevisionId]);
|
||||
}
|
||||
|
||||
async function getOption(req) {
|
||||
const name = req.params.name;
|
||||
const opt = await sql.getRow("SELECT * FROM options WHERE name = ?", [name]);
|
||||
|
||||
if (!opt.isSynced) {
|
||||
return [400, "This option can't be synced."];
|
||||
for (const {sync, entity} of entities) {
|
||||
await syncUpdateService.updateEntity(sync, entity, sourceId);
|
||||
}
|
||||
else {
|
||||
return opt;
|
||||
}
|
||||
}
|
||||
|
||||
async function getNoteReordering(req) {
|
||||
const parentNoteId = req.params.parentNoteId;
|
||||
|
||||
return {
|
||||
parentNoteId: parentNoteId,
|
||||
ordering: await sql.getMap("SELECT branchId, notePosition FROM branches WHERE parentNoteId = ? AND isDeleted = 0", [parentNoteId])
|
||||
};
|
||||
}
|
||||
|
||||
async function getRecentNote(req) {
|
||||
const branchId = req.params.branchId;
|
||||
|
||||
return await sql.getRow("SELECT * FROM recent_notes WHERE branchId = ?", [branchId]);
|
||||
}
|
||||
|
||||
async function getImage(req) {
|
||||
const imageId = req.params.imageId;
|
||||
const entity = await sql.getRow("SELECT * FROM images WHERE imageId = ?", [imageId]);
|
||||
|
||||
if (entity && entity.data !== null) {
|
||||
entity.data = entity.data.toString('base64');
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
async function getNoteImage(req) {
|
||||
const noteImageId = req.params.noteImageId;
|
||||
|
||||
return await sql.getRow("SELECT * FROM note_images WHERE noteImageId = ?", [noteImageId]);
|
||||
}
|
||||
|
||||
async function getLabel(req) {
|
||||
const labelId = req.params.labelId;
|
||||
|
||||
return await sql.getRow("SELECT * FROM labels WHERE labelId = ?", [labelId]);
|
||||
}
|
||||
|
||||
async function getApiToken(req) {
|
||||
const apiTokenId = req.params.apiTokenId;
|
||||
|
||||
return await sql.getRow("SELECT * FROM api_tokens WHERE apiTokenId = ?", [apiTokenId]);
|
||||
}
|
||||
|
||||
async function updateNote(req) {
|
||||
await syncUpdateService.updateNote(req.body.entity, req.body.sourceId);
|
||||
}
|
||||
|
||||
async function updateBranch(req) {
|
||||
await syncUpdateService.updateBranch(req.body.entity, req.body.sourceId);
|
||||
}
|
||||
|
||||
async function updateNoteRevision(req) {
|
||||
await syncUpdateService.updateNoteRevision(req.body.entity, req.body.sourceId);
|
||||
}
|
||||
|
||||
async function updateNoteReordering(req) {
|
||||
await syncUpdateService.updateNoteReordering(req.body.entity, req.body.sourceId);
|
||||
}
|
||||
|
||||
async function updateOption(req) {
|
||||
await syncUpdateService.updateOptions(req.body.entity, req.body.sourceId);
|
||||
}
|
||||
|
||||
async function updateRecentNote(req) {
|
||||
await syncUpdateService.updateRecentNotes(req.body.entity, req.body.sourceId);
|
||||
}
|
||||
|
||||
async function updateImage(req) {
|
||||
await syncUpdateService.updateImage(req.body.entity, req.body.sourceId);
|
||||
}
|
||||
|
||||
async function updateNoteImage(req) {
|
||||
await syncUpdateService.updateNoteImage(req.body.entity, req.body.sourceId);
|
||||
}
|
||||
|
||||
async function updateLabel(req) {
|
||||
await syncUpdateService.updateLabel(req.body.entity, req.body.sourceId);
|
||||
}
|
||||
|
||||
async function updateApiToken(req) {
|
||||
await syncUpdateService.updateApiToken(req.body.entity, req.body.sourceId);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@@ -187,24 +79,5 @@ module.exports = {
|
||||
forceFullSync,
|
||||
forceNoteSync,
|
||||
getChanged,
|
||||
getNote,
|
||||
getBranch,
|
||||
getImage,
|
||||
getNoteImage,
|
||||
getNoteReordering,
|
||||
getNoteRevision,
|
||||
getRecentNote,
|
||||
getOption,
|
||||
getLabel,
|
||||
getApiToken,
|
||||
updateNote,
|
||||
updateBranch,
|
||||
updateImage,
|
||||
updateNoteImage,
|
||||
updateNoteReordering,
|
||||
updateNoteRevision,
|
||||
updateRecentNote,
|
||||
updateOption,
|
||||
updateLabel,
|
||||
updateApiToken
|
||||
update
|
||||
};
|
||||
@@ -19,6 +19,7 @@ function init(app) {
|
||||
|
||||
res.status = function(statusCode) {
|
||||
res.statusCode = statusCode;
|
||||
return res;
|
||||
};
|
||||
|
||||
res.send = function(obj) {
|
||||
|
||||
@@ -40,22 +40,22 @@ const cls = require('../services/cls');
|
||||
const sql = require('../services/sql');
|
||||
const protectedSessionService = require('../services/protected_session');
|
||||
|
||||
function apiResultHandler(res, result) {
|
||||
function apiResultHandler(req, res, result) {
|
||||
// if it's an array and first element is integer then we consider this to be [statusCode, response] format
|
||||
if (Array.isArray(result) && result.length > 0 && Number.isInteger(result[0])) {
|
||||
const [statusCode, response] = result;
|
||||
|
||||
res.status(statusCode).send(response);
|
||||
|
||||
if (statusCode !== 200) {
|
||||
log.info(`${method} ${path} returned ${statusCode} with response ${JSON.stringify(response)}`);
|
||||
if (statusCode !== 200 && statusCode !== 201 && statusCode !== 204) {
|
||||
log.info(`${req.method} ${req.originalUrl} returned ${statusCode} with response ${JSON.stringify(response)}`);
|
||||
}
|
||||
}
|
||||
else if (result === undefined) {
|
||||
res.status(204).send();
|
||||
}
|
||||
else {
|
||||
res.status(200).send(result);
|
||||
res.send(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,13 +70,13 @@ function route(method, path, middleware, routeHandler, resultHandler) {
|
||||
cls.namespace.set('sourceId', req.headers.source_id);
|
||||
protectedSessionService.setProtectedSessionId(req);
|
||||
|
||||
return await sql.doInTransaction(async () => {
|
||||
return await sql.transactional(async () => {
|
||||
return await routeHandler(req, res, next);
|
||||
});
|
||||
});
|
||||
|
||||
if (resultHandler) {
|
||||
resultHandler(res, result);
|
||||
resultHandler(req, res, result);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
@@ -147,25 +147,7 @@ function register(app) {
|
||||
apiRoute(POST, '/api/sync/force-full-sync', syncApiRoute.forceFullSync);
|
||||
apiRoute(POST, '/api/sync/force-note-sync/:noteId', syncApiRoute.forceNoteSync);
|
||||
apiRoute(GET, '/api/sync/changed', syncApiRoute.getChanged);
|
||||
apiRoute(GET, '/api/sync/notes/:noteId', syncApiRoute.getNote);
|
||||
apiRoute(GET, '/api/sync/branches/:branchId', syncApiRoute.getBranch);
|
||||
apiRoute(GET, '/api/sync/note_revisions/:noteRevisionId', syncApiRoute.getNoteRevision);
|
||||
apiRoute(GET, '/api/sync/options/:name', syncApiRoute.getOption);
|
||||
apiRoute(GET, '/api/sync/note_reordering/:parentNoteId', syncApiRoute.getNoteReordering);
|
||||
apiRoute(GET, '/api/sync/recent_notes/:branchId', syncApiRoute.getRecentNote);
|
||||
apiRoute(GET, '/api/sync/images/:imageId', syncApiRoute.getImage);
|
||||
apiRoute(GET, '/api/sync/note_images/:noteImageId', syncApiRoute.getNoteImage);
|
||||
apiRoute(GET, '/api/sync/labels/:labelId', syncApiRoute.getLabel);
|
||||
apiRoute(GET, '/api/sync/api_tokens/:apiTokenId', syncApiRoute.getApiToken);
|
||||
apiRoute(PUT, '/api/sync/notes', syncApiRoute.updateNote);
|
||||
apiRoute(PUT, '/api/sync/note_revisions', syncApiRoute.updateNoteRevision);
|
||||
apiRoute(PUT, '/api/sync/note_reordering', syncApiRoute.updateNoteReordering);
|
||||
apiRoute(PUT, '/api/sync/options', syncApiRoute.updateOption);
|
||||
apiRoute(PUT, '/api/sync/recent_notes', syncApiRoute.updateRecentNote);
|
||||
apiRoute(PUT, '/api/sync/images', syncApiRoute.updateImage);
|
||||
apiRoute(PUT, '/api/sync/note_images', syncApiRoute.updateNoteImage);
|
||||
apiRoute(PUT, '/api/sync/labels', syncApiRoute.updateLabel);
|
||||
apiRoute(PUT, '/api/sync/api_tokens', syncApiRoute.updateApiToken);
|
||||
apiRoute(PUT, '/api/sync/update', syncApiRoute.update);
|
||||
|
||||
apiRoute(GET, '/api/event-log', eventLogRoute.getEventLog);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
const build = require('./build');
|
||||
const packageJson = require('../../package');
|
||||
|
||||
const APP_DB_VERSION = 86;
|
||||
const APP_DB_VERSION = 88;
|
||||
|
||||
module.exports = {
|
||||
appVersion: packageJson.version,
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { buildDate:"2018-01-17T23:59:03-05:00", buildRevision: "651a9fb3272c85d287c16d5a4978464fb7d2490d" };
|
||||
module.exports = { buildDate:"2018-04-14T08:28:50-04:00", buildRevision: "d57057ba28d2d93ffaeed15900116836fc791968" };
|
||||
|
||||
@@ -17,7 +17,7 @@ async function changePassword(currentPassword, newPassword) {
|
||||
const newPasswordVerificationKey = utils.toBase64(await myScryptService.getVerificationHash(newPassword));
|
||||
const decryptedDataKey = await passwordEncryptionService.getDataKey(currentPassword);
|
||||
|
||||
await sql.doInTransaction(async () => {
|
||||
await sql.transactional(async () => {
|
||||
await passwordEncryptionService.setDataKey(newPassword, decryptedDataKey);
|
||||
|
||||
await optionService.setOption('passwordVerificationHash', newPasswordVerificationKey);
|
||||
|
||||
@@ -197,16 +197,6 @@ async function runAllChecks() {
|
||||
AND images.isDeleted = 1`,
|
||||
"Note image is not deleted while image is deleted for noteImageId", errorList);
|
||||
|
||||
await runCheck(`
|
||||
SELECT
|
||||
noteId
|
||||
FROM
|
||||
notes
|
||||
WHERE
|
||||
isDeleted = 0
|
||||
AND (title IS NULL OR content IS NULL)`,
|
||||
"Note has null title or text", errorList);
|
||||
|
||||
await runCheck(`
|
||||
SELECT
|
||||
noteId
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
const sql = require('./sql');
|
||||
const utils = require('./utils');
|
||||
const log = require('./log');
|
||||
const eventLogService = require('./event_log');
|
||||
const messagingService = require('./messaging');
|
||||
|
||||
function getHash(rows) {
|
||||
let hash = '';
|
||||
@@ -121,6 +125,29 @@ async function getHashes() {
|
||||
return hashes;
|
||||
}
|
||||
|
||||
async function checkContentHashes(otherHashes) {
|
||||
const hashes = await getHashes();
|
||||
let allChecksPassed = true;
|
||||
|
||||
for (const key in hashes) {
|
||||
if (hashes[key] !== otherHashes[key]) {
|
||||
allChecksPassed = false;
|
||||
|
||||
await eventLogService.addEvent(`Content hash check for ${key} FAILED. Local is ${hashes[key]}, remote is ${resp.hashes[key]}`);
|
||||
|
||||
if (key !== 'recent_notes') {
|
||||
// let's not get alarmed about recent notes which get updated often and can cause failures in race conditions
|
||||
await messagingService.sendMessageToAllClients({type: 'sync-hash-check-failed'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allChecksPassed) {
|
||||
log.info("Content hash checks PASSED");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getHashes
|
||||
getHashes,
|
||||
checkContentHashes
|
||||
};
|
||||
@@ -4,6 +4,7 @@ const sql = require('./sql');
|
||||
const noteService = require('./notes');
|
||||
const labelService = require('./labels');
|
||||
const dateUtils = require('./date_utils');
|
||||
const repository = require('./repository');
|
||||
|
||||
const CALENDAR_ROOT_LABEL = 'calendarRoot';
|
||||
const YEAR_LABEL = 'yearNote';
|
||||
@@ -14,117 +15,112 @@ const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Satur
|
||||
const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
|
||||
|
||||
async function createNote(parentNoteId, noteTitle, noteText) {
|
||||
const {note} = await noteService.createNewNote(parentNoteId, {
|
||||
return (await noteService.createNewNote(parentNoteId, {
|
||||
title: noteTitle,
|
||||
content: noteText,
|
||||
target: 'into',
|
||||
isProtected: false
|
||||
});
|
||||
|
||||
return note.noteId;
|
||||
})).note;
|
||||
}
|
||||
|
||||
async function getNoteStartingWith(parentNoteId, startsWith) {
|
||||
return await sql.getValue(`SELECT noteId FROM notes JOIN branches USING(noteId)
|
||||
return await repository.getEntity(`SELECT notes.* FROM notes JOIN branches USING(noteId)
|
||||
WHERE parentNoteId = ? AND title LIKE '${startsWith}%'
|
||||
AND notes.isDeleted = 0 AND isProtected = 0
|
||||
AND branches.isDeleted = 0`, [parentNoteId]);
|
||||
}
|
||||
|
||||
async function getRootCalendarNoteId() {
|
||||
let rootNoteId = await sql.getValue(`SELECT notes.noteId FROM notes JOIN labels USING(noteId)
|
||||
WHERE labels.name = '${CALENDAR_ROOT_LABEL}' AND notes.isDeleted = 0`);
|
||||
async function getRootCalendarNote() {
|
||||
let rootNote = await labelService.getNoteWithLabel(CALENDAR_ROOT_LABEL);
|
||||
|
||||
if (!rootNoteId) {
|
||||
const {rootNote} = await noteService.createNewNote('root', {
|
||||
if (!rootNote) {
|
||||
rootNote = (await noteService.createNewNote('root', {
|
||||
title: 'Calendar',
|
||||
target: 'into',
|
||||
isProtected: false
|
||||
});
|
||||
})).note;
|
||||
|
||||
const rootNoteId = rootNote.noteId;
|
||||
|
||||
await labelService.createLabel(rootNoteId, CALENDAR_ROOT_LABEL);
|
||||
await labelService.createLabel(rootNote.noteId, CALENDAR_ROOT_LABEL);
|
||||
}
|
||||
|
||||
return rootNoteId;
|
||||
return rootNote;
|
||||
}
|
||||
|
||||
async function getYearNoteId(dateTimeStr, rootNoteId) {
|
||||
async function getYearNote(dateTimeStr, rootNote) {
|
||||
const yearStr = dateTimeStr.substr(0, 4);
|
||||
|
||||
let yearNoteId = await labelService.getNoteIdWithLabel(YEAR_LABEL, yearStr);
|
||||
let yearNote = await labelService.getNoteWithLabel(YEAR_LABEL, yearStr);
|
||||
|
||||
if (!yearNoteId) {
|
||||
yearNoteId = await getNoteStartingWith(rootNoteId, yearStr);
|
||||
if (!yearNote) {
|
||||
yearNote = await getNoteStartingWith(rootNote.noteId, yearStr);
|
||||
|
||||
if (!yearNoteId) {
|
||||
yearNoteId = await createNote(rootNoteId, yearStr);
|
||||
if (!yearNote) {
|
||||
yearNote = await createNote(rootNote.noteId, yearStr);
|
||||
}
|
||||
|
||||
await labelService.createLabel(yearNoteId, YEAR_LABEL, yearStr);
|
||||
await labelService.createLabel(yearNote.noteId, YEAR_LABEL, yearStr);
|
||||
}
|
||||
|
||||
return yearNoteId;
|
||||
return yearNote;
|
||||
}
|
||||
|
||||
async function getMonthNoteId(dateTimeStr, rootNoteId) {
|
||||
async function getMonthNote(dateTimeStr, rootNote) {
|
||||
const monthStr = dateTimeStr.substr(0, 7);
|
||||
const monthNumber = dateTimeStr.substr(5, 2);
|
||||
|
||||
let monthNoteId = await labelService.getNoteIdWithLabel(MONTH_LABEL, monthStr);
|
||||
let monthNote = await labelService.getNoteWithLabel(MONTH_LABEL, monthStr);
|
||||
|
||||
if (!monthNoteId) {
|
||||
const yearNoteId = await getYearNoteId(dateTimeStr, rootNoteId);
|
||||
if (!monthNote) {
|
||||
const yearNote = await getYearNote(dateTimeStr, rootNote);
|
||||
|
||||
monthNoteId = await getNoteStartingWith(yearNoteId, monthNumber);
|
||||
monthNote = await getNoteStartingWith(yearNote.noteId, monthNumber);
|
||||
|
||||
if (!monthNoteId) {
|
||||
if (!monthNote) {
|
||||
const dateObj = dateUtils.parseDate(dateTimeStr);
|
||||
|
||||
const noteTitle = monthNumber + " - " + MONTHS[dateObj.getMonth()];
|
||||
|
||||
monthNoteId = await createNote(yearNoteId, noteTitle);
|
||||
monthNote = await createNote(yearNote.noteId, noteTitle);
|
||||
}
|
||||
|
||||
await labelService.createLabel(monthNoteId, MONTH_LABEL, monthStr);
|
||||
await labelService.createLabel(monthNote.noteId, MONTH_LABEL, monthStr);
|
||||
}
|
||||
|
||||
return monthNoteId;
|
||||
return monthNote;
|
||||
}
|
||||
|
||||
async function getDateNoteId(dateTimeStr, rootNoteId = null) {
|
||||
if (!rootNoteId) {
|
||||
rootNoteId = await getRootCalendarNoteId();
|
||||
async function getDateNote(dateTimeStr, rootNote = null) {
|
||||
if (!rootNote) {
|
||||
rootNote = await getRootCalendarNote();
|
||||
}
|
||||
|
||||
const dateStr = dateTimeStr.substr(0, 10);
|
||||
const dayNumber = dateTimeStr.substr(8, 2);
|
||||
|
||||
let dateNoteId = await labelService.getNoteIdWithLabel(DATE_LABEL, dateStr);
|
||||
let dateNote = await labelService.getNoteWithLabel(DATE_LABEL, dateStr);
|
||||
|
||||
if (!dateNoteId) {
|
||||
const monthNoteId = await getMonthNoteId(dateTimeStr, rootNoteId);
|
||||
if (!dateNote) {
|
||||
const monthNote = await getMonthNote(dateTimeStr, rootNote);
|
||||
|
||||
dateNoteId = await getNoteStartingWith(monthNoteId, dayNumber);
|
||||
dateNote = await getNoteStartingWith(monthNote.noteId, dayNumber);
|
||||
|
||||
if (!dateNoteId) {
|
||||
if (!dateNote) {
|
||||
const dateObj = dateUtils.parseDate(dateTimeStr);
|
||||
|
||||
const noteTitle = dayNumber + " - " + DAYS[dateObj.getDay()];
|
||||
|
||||
dateNoteId = await createNote(monthNoteId, noteTitle);
|
||||
dateNote = await createNote(monthNote.noteId, noteTitle);
|
||||
}
|
||||
|
||||
await labelService.createLabel(dateNoteId, DATE_LABEL, dateStr);
|
||||
await labelService.createLabel(dateNote.noteId, DATE_LABEL, dateStr);
|
||||
}
|
||||
|
||||
return dateNoteId;
|
||||
return dateNote;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getRootCalendarNoteId,
|
||||
getYearNoteId,
|
||||
getMonthNoteId,
|
||||
getDateNoteId
|
||||
getRootCalendarNote,
|
||||
getYearNote,
|
||||
getMonthNote,
|
||||
getDateNote
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
const sql = require('./sql');
|
||||
const repository = require('./repository');
|
||||
const Label = require('../entities/label');
|
||||
|
||||
@@ -12,17 +11,10 @@ const BUILTIN_LABELS = [
|
||||
'run',
|
||||
'manualTransactionHandling',
|
||||
'disableInclusion',
|
||||
'appCss'
|
||||
'appCss',
|
||||
'hideChildrenOverview'
|
||||
];
|
||||
|
||||
async function getNoteIdWithLabel(name, value) {
|
||||
return await sql.getValue(`SELECT notes.noteId FROM notes JOIN labels USING(noteId)
|
||||
WHERE notes.isDeleted = 0
|
||||
AND labels.isDeleted = 0
|
||||
AND labels.name = ?
|
||||
AND labels.value = ?`, [name, value]);
|
||||
}
|
||||
|
||||
async function getNotesWithLabel(name, value) {
|
||||
let notes;
|
||||
|
||||
@@ -44,11 +36,6 @@ async function getNoteWithLabel(name, value) {
|
||||
return notes.length > 0 ? notes[0] : null;
|
||||
}
|
||||
|
||||
async function getNoteIdsWithLabel(name) {
|
||||
return await sql.getColumn(`SELECT DISTINCT notes.noteId FROM notes JOIN labels USING(noteId)
|
||||
WHERE notes.isDeleted = 0 AND labels.isDeleted = 0 AND labels.name = ? AND labels.isDeleted = 0`, [name]);
|
||||
}
|
||||
|
||||
async function createLabel(noteId, name, value = "") {
|
||||
return await new Label({
|
||||
noteId: noteId,
|
||||
@@ -58,10 +45,8 @@ async function createLabel(noteId, name, value = "") {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getNoteIdWithLabel,
|
||||
getNotesWithLabel,
|
||||
getNoteWithLabel,
|
||||
getNoteIdsWithLabel,
|
||||
createLabel,
|
||||
BUILTIN_LABELS
|
||||
};
|
||||
@@ -15,14 +15,22 @@ const logger = require('simple-node-logger').createRollingFileLogger({
|
||||
});
|
||||
|
||||
function info(message) {
|
||||
logger.info(message);
|
||||
// info messages are logged asynchronously
|
||||
setTimeout(() => {
|
||||
console.log(message);
|
||||
|
||||
console.log(message);
|
||||
logger.info(message);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function error(message) {
|
||||
message = "ERROR: " + message;
|
||||
|
||||
// we're using .info() instead of .error() because simple-node-logger emits weird error for showError()
|
||||
info("ERROR: " + message);
|
||||
// errors are logged synchronously to make sure it doesn't get lost in case of crash
|
||||
logger.info(message);
|
||||
|
||||
console.trace(message);
|
||||
}
|
||||
|
||||
const requestBlacklist = [ "/libraries", "/javascripts", "/images", "/stylesheets" ];
|
||||
|
||||
@@ -45,7 +45,7 @@ async function migrate() {
|
||||
// needs to happen outside of the transaction (otherwise it's a NO-OP)
|
||||
await sql.execute("PRAGMA foreign_keys = OFF");
|
||||
|
||||
await sql.doInTransaction(async () => {
|
||||
await sql.transactional(async () => {
|
||||
if (mig.type === 'sql') {
|
||||
const migrationSql = fs.readFileSync(resourceDir.MIGRATIONS_DIR + "/" + mig.file).toString('utf8');
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ async function createNewNote(parentNoteId, noteData) {
|
||||
noteId: note.noteId,
|
||||
parentNoteId: parentNoteId,
|
||||
notePosition: newNotePos,
|
||||
prefix: noteData.prefix,
|
||||
isExpanded: 0
|
||||
}).save();
|
||||
|
||||
@@ -180,6 +181,8 @@ async function saveNoteRevision(note) {
|
||||
// title and text should be decrypted now
|
||||
title: note.title,
|
||||
content: note.content,
|
||||
type: note.type,
|
||||
mime: note.mime,
|
||||
isProtected: 0, // will be fixed in the protectNoteRevisions() call
|
||||
dateModifiedFrom: note.dateModified,
|
||||
dateModifiedTo: dateUtils.nowDate()
|
||||
@@ -198,7 +201,7 @@ async function updateNote(noteId, noteUpdates) {
|
||||
await saveNoteRevision(note);
|
||||
|
||||
note.title = noteUpdates.title;
|
||||
note.content = noteUpdates.content;
|
||||
note.setContent(noteUpdates.content);
|
||||
note.isProtected = noteUpdates.isProtected;
|
||||
await note.save();
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ async function updateEntity(entity) {
|
||||
|
||||
delete clone.jsonContent;
|
||||
|
||||
await sql.doInTransaction(async () => {
|
||||
await sql.transactional(async () => {
|
||||
await sql.replace(entity.constructor.tableName, clone);
|
||||
|
||||
const primaryKey = entity[entity.constructor.primaryKeyName];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const scriptService = require('./script');
|
||||
const repository = require('./repository');
|
||||
const cls = require('./cls');
|
||||
const sqlInit = require('./sql_init');
|
||||
|
||||
async function runNotesWithLabel(runAttrValue) {
|
||||
const notes = await repository.getEntities(`
|
||||
@@ -19,8 +20,10 @@ async function runNotesWithLabel(runAttrValue) {
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000);
|
||||
sqlInit.dbReady.then(() => {
|
||||
setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000);
|
||||
|
||||
setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000);
|
||||
setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000);
|
||||
|
||||
setInterval(cls.wrap(() => runNotesWithLabel('daily')), 24 * 3600 * 1000);
|
||||
setInterval(cls.wrap(() => runNotesWithLabel('daily')), 24 * 3600 * 1000);
|
||||
});
|
||||
@@ -27,7 +27,7 @@ async function executeBundle(bundle, startNote) {
|
||||
return await execute(ctx, script, '');
|
||||
}
|
||||
else {
|
||||
return await sql.doInTransaction(async () => execute(ctx, script, ''));
|
||||
return await sql.transactional(async () => execute(ctx, script, ''));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,10 +56,10 @@ function ScriptApi(startNote, currentNote) {
|
||||
|
||||
this.log = message => log.info(`Script ${currentNote.noteId}: ${message}`);
|
||||
|
||||
this.getRootCalendarNoteId = dateNoteService.getRootCalendarNoteId;
|
||||
this.getDateNoteId = dateNoteService.getDateNoteId;
|
||||
this.getRootCalendarNote = dateNoteService.getRootCalendarNote;
|
||||
this.getDateNote = dateNoteService.getDateNote;
|
||||
|
||||
this.transactional = sql.doInTransaction;
|
||||
this.transactional = sql.transactional;
|
||||
}
|
||||
|
||||
module.exports = ScriptContext;
|
||||
@@ -122,7 +122,7 @@ async function wrap(func) {
|
||||
let transactionActive = false;
|
||||
let transactionPromise = null;
|
||||
|
||||
async function doInTransaction(func) {
|
||||
async function transactional(func) {
|
||||
if (cls.namespace.get('isInTransaction')) {
|
||||
return await func();
|
||||
}
|
||||
@@ -149,11 +149,13 @@ async function doInTransaction(func) {
|
||||
resolve();
|
||||
}
|
||||
catch (e) {
|
||||
log.error("Error executing transaction, executing rollback. Inner exception: " + e.stack + error.stack);
|
||||
if (transactionActive) {
|
||||
log.error("Error executing transaction, executing rollback. Inner exception: " + e.stack + error.stack);
|
||||
|
||||
await rollback();
|
||||
await rollback();
|
||||
|
||||
transactionActive = false;
|
||||
transactionActive = false;
|
||||
}
|
||||
|
||||
reject(e);
|
||||
}
|
||||
@@ -181,5 +183,5 @@ module.exports = {
|
||||
getColumn,
|
||||
execute,
|
||||
executeScript,
|
||||
doInTransaction
|
||||
transactional
|
||||
};
|
||||
@@ -58,7 +58,7 @@ async function createInitialDatabase() {
|
||||
const imagesSql = fs.readFileSync(resourceDir.DB_INIT_DIR + '/main_images.sql', 'UTF-8');
|
||||
const notesImageSql = fs.readFileSync(resourceDir.DB_INIT_DIR + '/main_note_images.sql', 'UTF-8');
|
||||
|
||||
await sql.doInTransaction(async () => {
|
||||
await sql.transactional(async () => {
|
||||
await sql.executeScript(schema);
|
||||
await sql.executeScript(notesSql);
|
||||
await sql.executeScript(notesTreeSql);
|
||||
|
||||
@@ -10,10 +10,8 @@ const sourceIdService = require('./source_id');
|
||||
const dateUtils = require('./date_utils');
|
||||
const syncUpdateService = require('./sync_update');
|
||||
const contentHashService = require('./content_hash');
|
||||
const eventLogService = require('./event_log');
|
||||
const fs = require('fs');
|
||||
const appInfo = require('./app_info');
|
||||
const messagingService = require('./messaging');
|
||||
const syncSetup = require('./sync_setup');
|
||||
const syncMutexService = require('./sync_mutex');
|
||||
const cls = require('./cls');
|
||||
@@ -91,69 +89,19 @@ async function login() {
|
||||
return syncContext;
|
||||
}
|
||||
|
||||
async function getLastSyncedPull() {
|
||||
return parseInt(await optionService.getOption('lastSyncedPull'));
|
||||
}
|
||||
|
||||
async function setLastSyncedPull(syncId) {
|
||||
await optionService.setOption('lastSyncedPull', syncId);
|
||||
}
|
||||
|
||||
async function pullSync(syncContext) {
|
||||
const lastSyncedPull = await getLastSyncedPull();
|
||||
const changesUri = '/api/sync/changed?lastSyncId=' + await getLastSyncedPull();
|
||||
|
||||
const changesUri = '/api/sync/changed?lastSyncId=' + lastSyncedPull;
|
||||
const rows = await syncRequest(syncContext, 'GET', changesUri);
|
||||
|
||||
const syncRows = await syncRequest(syncContext, 'GET', changesUri);
|
||||
log.info("Pulled " + rows.length + " changes from " + changesUri);
|
||||
|
||||
log.info("Pulled " + syncRows.length + " changes from " + changesUri);
|
||||
|
||||
for (const sync of syncRows) {
|
||||
for (const {sync, entity} of rows) {
|
||||
if (sourceIdService.isLocalSourceId(sync.sourceId)) {
|
||||
log.info(`Skipping pull #${sync.id} ${sync.entityName} ${sync.entityId} because ${sync.sourceId} is a local source id.`);
|
||||
|
||||
await setLastSyncedPull(sync.id);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const resp = await syncRequest(syncContext, 'GET', "/api/sync/" + sync.entityName + "/" + encodeURIComponent(sync.entityId));
|
||||
|
||||
if (!resp || (sync.entityName === 'notes' && !resp.entity)) {
|
||||
log.error(`Empty response to pull for sync #${sync.id} ${sync.entityName}, id=${sync.entityId}`);
|
||||
}
|
||||
else if (sync.entityName === 'notes') {
|
||||
await syncUpdateService.updateNote(resp.entity, syncContext.sourceId);
|
||||
}
|
||||
else if (sync.entityName === 'branches') {
|
||||
await syncUpdateService.updateBranch(resp, syncContext.sourceId);
|
||||
}
|
||||
else if (sync.entityName === 'note_revisions') {
|
||||
await syncUpdateService.updateNoteRevision(resp, syncContext.sourceId);
|
||||
}
|
||||
else if (sync.entityName === 'note_reordering') {
|
||||
await syncUpdateService.updateNoteReordering(resp, syncContext.sourceId);
|
||||
}
|
||||
else if (sync.entityName === 'options') {
|
||||
await syncUpdateService.updateOptions(resp, syncContext.sourceId);
|
||||
}
|
||||
else if (sync.entityName === 'recent_notes') {
|
||||
await syncUpdateService.updateRecentNotes(resp, syncContext.sourceId);
|
||||
}
|
||||
else if (sync.entityName === 'images') {
|
||||
await syncUpdateService.updateImage(resp, syncContext.sourceId);
|
||||
}
|
||||
else if (sync.entityName === 'note_images') {
|
||||
await syncUpdateService.updateNoteImage(resp, syncContext.sourceId);
|
||||
}
|
||||
else if (sync.entityName === 'labels') {
|
||||
await syncUpdateService.updateLabel(resp, syncContext.sourceId);
|
||||
}
|
||||
else if (sync.entityName === 'api_tokens') {
|
||||
await syncUpdateService.updateApiToken(resp, syncContext.sourceId);
|
||||
}
|
||||
else {
|
||||
throw new Error(`Unrecognized entity type ${sync.entityName} in sync #${sync.id}`);
|
||||
await syncUpdateService.updateEntity(sync, entity, syncContext.sourceId);
|
||||
}
|
||||
|
||||
await setLastSyncedPull(sync.id);
|
||||
@@ -162,145 +110,69 @@ async function pullSync(syncContext) {
|
||||
log.info("Finished pull");
|
||||
}
|
||||
|
||||
async function getLastSyncedPush() {
|
||||
return parseInt(await optionService.getOption('lastSyncedPush'));
|
||||
}
|
||||
|
||||
async function setLastSyncedPush(lastSyncedPush) {
|
||||
await optionService.setOption('lastSyncedPush', lastSyncedPush);
|
||||
}
|
||||
|
||||
async function pushSync(syncContext) {
|
||||
let lastSyncedPush = await getLastSyncedPush();
|
||||
|
||||
while (true) {
|
||||
const sync = await sql.getRowOrNull('SELECT * FROM sync WHERE id > ? LIMIT 1', [lastSyncedPush]);
|
||||
const syncs = await sql.getRows('SELECT * FROM sync WHERE id > ? LIMIT 1000', [lastSyncedPush]);
|
||||
|
||||
if (sync === null) {
|
||||
// nothing to sync
|
||||
const filteredSyncs = syncs.filter(sync => {
|
||||
if (sync.sourceId === syncContext.sourceId) {
|
||||
log.info(`Skipping push #${sync.id} ${sync.entityName} ${sync.entityId} because it originates from sync target`);
|
||||
|
||||
// this may set lastSyncedPush beyond what's actually sent (because of size limit)
|
||||
// so this is applied to the database only if there's no actual update
|
||||
// TODO: it would be better to simplify this somehow
|
||||
lastSyncedPush = sync.id;
|
||||
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (filteredSyncs.length === 0) {
|
||||
log.info("Nothing to push");
|
||||
|
||||
await setLastSyncedPush(lastSyncedPush);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (sync.sourceId === syncContext.sourceId) {
|
||||
log.info(`Skipping push #${sync.id} ${sync.entityName} ${sync.entityId} because it originates from sync target`);
|
||||
}
|
||||
else {
|
||||
await pushEntity(sync, syncContext);
|
||||
}
|
||||
const syncRecords = await getSyncRecords(filteredSyncs);
|
||||
|
||||
lastSyncedPush = sync.id;
|
||||
log.info(`Pushing ${syncRecords.length} syncs.`);
|
||||
|
||||
await syncRequest(syncContext, 'PUT', '/api/sync/update', {
|
||||
sourceId: sourceIdService.getCurrentSourceId(),
|
||||
entities: syncRecords
|
||||
});
|
||||
|
||||
lastSyncedPush = syncRecords[syncRecords.length - 1].sync.id;
|
||||
|
||||
await setLastSyncedPush(lastSyncedPush);
|
||||
}
|
||||
}
|
||||
|
||||
async function pushEntity(sync, syncContext) {
|
||||
let entity;
|
||||
|
||||
if (sync.entityName === 'notes') {
|
||||
entity = await sql.getRow('SELECT * FROM notes WHERE noteId = ?', [sync.entityId]);
|
||||
|
||||
serializeNoteContentBuffer(entity);
|
||||
}
|
||||
else if (sync.entityName === 'branches') {
|
||||
entity = await sql.getRow('SELECT * FROM branches WHERE branchId = ?', [sync.entityId]);
|
||||
}
|
||||
else if (sync.entityName === 'note_revisions') {
|
||||
entity = await sql.getRow('SELECT * FROM note_revisions WHERE noteRevisionId = ?', [sync.entityId]);
|
||||
}
|
||||
else if (sync.entityName === 'note_reordering') {
|
||||
entity = {
|
||||
parentNoteId: sync.entityId,
|
||||
ordering: await sql.getMap('SELECT branchId, notePosition FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [sync.entityId])
|
||||
};
|
||||
}
|
||||
else if (sync.entityName === 'options') {
|
||||
entity = await sql.getRow('SELECT * FROM options WHERE name = ?', [sync.entityId]);
|
||||
}
|
||||
else if (sync.entityName === 'recent_notes') {
|
||||
entity = await sql.getRow('SELECT * FROM recent_notes WHERE branchId = ?', [sync.entityId]);
|
||||
}
|
||||
else if (sync.entityName === 'images') {
|
||||
entity = await sql.getRow('SELECT * FROM images WHERE imageId = ?', [sync.entityId]);
|
||||
|
||||
if (entity.data !== null) {
|
||||
entity.data = entity.data.toString('base64');
|
||||
}
|
||||
}
|
||||
else if (sync.entityName === 'note_images') {
|
||||
entity = await sql.getRow('SELECT * FROM note_images WHERE noteImageId = ?', [sync.entityId]);
|
||||
}
|
||||
else if (sync.entityName === 'labels') {
|
||||
entity = await sql.getRow('SELECT * FROM labels WHERE labelId = ?', [sync.entityId]);
|
||||
}
|
||||
else if (sync.entityName === 'api_tokens') {
|
||||
entity = await sql.getRow('SELECT * FROM api_tokens WHERE apiTokenId = ?', [sync.entityId]);
|
||||
}
|
||||
else {
|
||||
throw new Error(`Unrecognized entity type ${sync.entityName} in sync #${sync.id}`);
|
||||
}
|
||||
|
||||
if (!entity) {
|
||||
log.info(`Sync #${sync.id} entity for ${sync.entityName} ${sync.entityId} doesn't exist. Skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(`Pushing changes in sync #${sync.id} ${sync.entityName} ${sync.entityId}`);
|
||||
|
||||
const payload = {
|
||||
sourceId: sourceIdService.getCurrentSourceId(),
|
||||
entity: entity
|
||||
};
|
||||
|
||||
await syncRequest(syncContext, 'PUT', '/api/sync/' + sync.entityName, payload);
|
||||
}
|
||||
|
||||
function serializeNoteContentBuffer(note) {
|
||||
if (note.type === 'file') {
|
||||
note.content = note.content.toString("binary");
|
||||
}
|
||||
}
|
||||
|
||||
async function checkContentHash(syncContext) {
|
||||
const resp = await syncRequest(syncContext, 'GET', '/api/sync/check');
|
||||
|
||||
if (await getLastSyncedPull() < resp.max_sync_id) {
|
||||
if (await getLastSyncedPull() < resp.maxSyncId) {
|
||||
log.info("There are some outstanding pulls, skipping content check.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const lastSyncedPush = await getLastSyncedPush();
|
||||
const notPushedSyncs = await sql.getValue("SELECT COUNT(*) FROM sync WHERE id > ?", [lastSyncedPush]);
|
||||
const notPushedSyncs = await sql.getValue("SELECT COUNT(*) FROM sync WHERE id > ?", [await getLastSyncedPush()]);
|
||||
|
||||
if (notPushedSyncs > 0) {
|
||||
log.info("There's " + notPushedSyncs + " outstanding pushes, skipping content check.");
|
||||
log.info(`There's ${notPushedSyncs} outstanding pushes, skipping content check.`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const hashes = await contentHashService.getHashes();
|
||||
let allChecksPassed = true;
|
||||
|
||||
for (const key in hashes) {
|
||||
if (hashes[key] !== resp.hashes[key]) {
|
||||
allChecksPassed = false;
|
||||
|
||||
await eventLogService.addEvent(`Content hash check for ${key} FAILED. Local is ${hashes[key]}, remote is ${resp.hashes[key]}`);
|
||||
|
||||
if (key !== 'recent_notes') {
|
||||
// let's not get alarmed about recent notes which get updated often and can cause failures in race conditions
|
||||
await messagingService.sendMessageToAllClients({type: 'sync-hash-check-failed'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allChecksPassed) {
|
||||
log.info("Content hash checks PASSED");
|
||||
}
|
||||
await contentHashService.checkContentHashes(resp.hashes);
|
||||
}
|
||||
|
||||
async function syncRequest(syncContext, method, uri, body) {
|
||||
@@ -331,6 +203,80 @@ async function syncRequest(syncContext, method, uri, body) {
|
||||
}
|
||||
}
|
||||
|
||||
const primaryKeys = {
|
||||
"notes": "noteId",
|
||||
"branches": "branchId",
|
||||
"note_revisions": "noteRevisionId",
|
||||
"option": "name",
|
||||
"recent_notes": "branchId",
|
||||
"images": "imageId",
|
||||
"note_images": "noteImageId",
|
||||
"labels": "labelId",
|
||||
"api_tokens": "apiTokenId"
|
||||
};
|
||||
|
||||
async function getEntityRow(entityName, entityId) {
|
||||
if (entityName === 'note_reordering') {
|
||||
return await sql.getMap("SELECT branchId, notePosition FROM branches WHERE parentNoteId = ? AND isDeleted = 0", [entityId]);
|
||||
}
|
||||
else {
|
||||
const primaryKey = primaryKeys[entityName];
|
||||
|
||||
if (!primaryKey) {
|
||||
throw new Error("Unknown entity " + entityName);
|
||||
}
|
||||
|
||||
const entity = await sql.getRow(`SELECT * FROM ${entityName} WHERE ${primaryKey} = ?`, [entityId]);
|
||||
|
||||
if (entityName === 'notes' && entity.type === 'file') {
|
||||
entity.content = entity.content.toString("binary");
|
||||
}
|
||||
else if (entityName === 'images') {
|
||||
entity.data = entity.data.toString('base64');
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
async function getSyncRecords(syncs) {
|
||||
const records = [];
|
||||
let length = 0;
|
||||
|
||||
for (const sync of syncs) {
|
||||
const record = {
|
||||
sync: sync,
|
||||
entity: await getEntityRow(sync.entityName, sync.entityId)
|
||||
};
|
||||
|
||||
records.push(record);
|
||||
|
||||
length += JSON.stringify(record).length;
|
||||
|
||||
if (length > 1000000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
async function getLastSyncedPull() {
|
||||
return parseInt(await optionService.getOption('lastSyncedPull'));
|
||||
}
|
||||
|
||||
async function setLastSyncedPull(syncId) {
|
||||
await optionService.setOption('lastSyncedPull', syncId);
|
||||
}
|
||||
|
||||
async function getLastSyncedPush() {
|
||||
return parseInt(await optionService.getOption('lastSyncedPush'));
|
||||
}
|
||||
|
||||
async function setLastSyncedPush(lastSyncedPush) {
|
||||
await optionService.setOption('lastSyncedPush', lastSyncedPush);
|
||||
}
|
||||
|
||||
sqlInit.dbReady.then(() => {
|
||||
if (syncSetup.isSyncSetup) {
|
||||
log.info("Setting up sync to " + syncSetup.SYNC_SERVER + " with timeout " + syncSetup.SYNC_TIMEOUT);
|
||||
@@ -357,5 +303,5 @@ sqlInit.dbReady.then(() => {
|
||||
|
||||
module.exports = {
|
||||
sync,
|
||||
serializeNoteContentBuffer
|
||||
getSyncRecords
|
||||
};
|
||||
@@ -91,6 +91,8 @@ async function fillSyncRows(entityName, entityKey) {
|
||||
}
|
||||
|
||||
async function fillAllSyncRows() {
|
||||
await sql.execute("DELETE FROM sync");
|
||||
|
||||
await fillSyncRows("notes", "noteId");
|
||||
await fillSyncRows("branches", "branchId");
|
||||
await fillSyncRows("note_revisions", "noteRevisionId");
|
||||
|
||||
@@ -3,6 +3,44 @@ const log = require('./log');
|
||||
const eventLogService = require('./event_log');
|
||||
const syncTableService = require('./sync_table');
|
||||
|
||||
async function updateEntity(sync, entity, sourceId) {
|
||||
const {entityName} = sync;
|
||||
|
||||
if (entityName === 'notes') {
|
||||
await updateNote(entity, sourceId);
|
||||
}
|
||||
else if (entityName === 'branches') {
|
||||
await updateBranch(entity, sourceId);
|
||||
}
|
||||
else if (entityName === 'note_revisions') {
|
||||
await updateNoteRevision(entity, sourceId);
|
||||
}
|
||||
else if (entityName === 'note_reordering') {
|
||||
await updateNoteReordering(sync.entityId, entity, sourceId);
|
||||
}
|
||||
else if (entityName === 'options') {
|
||||
await updateOptions(entity, sourceId);
|
||||
}
|
||||
else if (entityName === 'recent_notes') {
|
||||
await updateRecentNotes(entity, sourceId);
|
||||
}
|
||||
else if (entityName === 'images') {
|
||||
await updateImage(entity, sourceId);
|
||||
}
|
||||
else if (entityName === 'note_images') {
|
||||
await updateNoteImage(entity, sourceId);
|
||||
}
|
||||
else if (entityName === 'labels') {
|
||||
await updateLabel(entity, sourceId);
|
||||
}
|
||||
else if (entityName === 'api_tokens') {
|
||||
await updateApiToken(entity, sourceId);
|
||||
}
|
||||
else {
|
||||
throw new Error(`Unrecognized entity type ${sync}`);
|
||||
}
|
||||
}
|
||||
|
||||
function deserializeNoteContentBuffer(note) {
|
||||
if (note.type === 'file') {
|
||||
note.content = new Buffer(note.content, 'binary');
|
||||
@@ -15,7 +53,7 @@ async function updateNote(entity, sourceId) {
|
||||
const origNote = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [entity.noteId]);
|
||||
|
||||
if (!origNote || origNote.dateModified <= entity.dateModified) {
|
||||
await sql.doInTransaction(async () => {
|
||||
await sql.transactional(async () => {
|
||||
await sql.replace("notes", entity);
|
||||
|
||||
await syncTableService.addNoteSync(entity.noteId, sourceId);
|
||||
@@ -29,7 +67,7 @@ async function updateNote(entity, sourceId) {
|
||||
async function updateBranch(entity, sourceId) {
|
||||
const orig = await sql.getRowOrNull("SELECT * FROM branches WHERE branchId = ?", [entity.branchId]);
|
||||
|
||||
await sql.doInTransaction(async () => {
|
||||
await sql.transactional(async () => {
|
||||
if (orig === null || orig.dateModified < entity.dateModified) {
|
||||
delete entity.isExpanded;
|
||||
|
||||
@@ -45,7 +83,7 @@ async function updateBranch(entity, sourceId) {
|
||||
async function updateNoteRevision(entity, sourceId) {
|
||||
const orig = await sql.getRowOrNull("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [entity.noteRevisionId]);
|
||||
|
||||
await sql.doInTransaction(async () => {
|
||||
await sql.transactional(async () => {
|
||||
// we update note revision even if date modified to is the same because the only thing which might have changed
|
||||
// is the protected status (and correnspondingly title and content) which doesn't affect the dateModifiedTo
|
||||
if (orig === null || orig.dateModifiedTo <= entity.dateModifiedTo) {
|
||||
@@ -58,13 +96,13 @@ async function updateNoteRevision(entity, sourceId) {
|
||||
});
|
||||
}
|
||||
|
||||
async function updateNoteReordering(entity, sourceId) {
|
||||
await sql.doInTransaction(async () => {
|
||||
Object.keys(entity.ordering).forEach(async key => {
|
||||
await sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity.ordering[key], key]);
|
||||
async function updateNoteReordering(entityId, entity, sourceId) {
|
||||
await sql.transactional(async () => {
|
||||
Object.keys(entity).forEach(async key => {
|
||||
await sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity[key], key]);
|
||||
});
|
||||
|
||||
await syncTableService.addNoteReorderingSync(entity.parentNoteId, sourceId);
|
||||
await syncTableService.addNoteReorderingSync(entityId, sourceId);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -75,7 +113,7 @@ async function updateOptions(entity, sourceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
await sql.doInTransaction(async () => {
|
||||
await sql.transactional(async () => {
|
||||
if (orig === null || orig.dateModified < entity.dateModified) {
|
||||
await sql.replace('options', entity);
|
||||
|
||||
@@ -90,7 +128,7 @@ async function updateRecentNotes(entity, sourceId) {
|
||||
const orig = await sql.getRowOrNull("SELECT * FROM recent_notes WHERE branchId = ?", [entity.branchId]);
|
||||
|
||||
if (orig === null || orig.dateAccessed < entity.dateAccessed) {
|
||||
await sql.doInTransaction(async () => {
|
||||
await sql.transactional(async () => {
|
||||
await sql.replace('recent_notes', entity);
|
||||
|
||||
await syncTableService.addRecentNoteSync(entity.branchId, sourceId);
|
||||
@@ -106,7 +144,7 @@ async function updateImage(entity, sourceId) {
|
||||
const origImage = await sql.getRow("SELECT * FROM images WHERE imageId = ?", [entity.imageId]);
|
||||
|
||||
if (!origImage || origImage.dateModified <= entity.dateModified) {
|
||||
await sql.doInTransaction(async () => {
|
||||
await sql.transactional(async () => {
|
||||
await sql.replace("images", entity);
|
||||
|
||||
await syncTableService.addImageSync(entity.imageId, sourceId);
|
||||
@@ -120,7 +158,7 @@ async function updateNoteImage(entity, sourceId) {
|
||||
const origNoteImage = await sql.getRow("SELECT * FROM note_images WHERE noteImageId = ?", [entity.noteImageId]);
|
||||
|
||||
if (!origNoteImage || origNoteImage.dateModified <= entity.dateModified) {
|
||||
await sql.doInTransaction(async () => {
|
||||
await sql.transactional(async () => {
|
||||
await sql.replace("note_images", entity);
|
||||
|
||||
await syncTableService.addNoteImageSync(entity.noteImageId, sourceId);
|
||||
@@ -134,7 +172,7 @@ async function updateLabel(entity, sourceId) {
|
||||
const origLabel = await sql.getRow("SELECT * FROM labels WHERE labelId = ?", [entity.labelId]);
|
||||
|
||||
if (!origLabel || origLabel.dateModified <= entity.dateModified) {
|
||||
await sql.doInTransaction(async () => {
|
||||
await sql.transactional(async () => {
|
||||
await sql.replace("labels", entity);
|
||||
|
||||
await syncTableService.addLabelSync(entity.labelId, sourceId);
|
||||
@@ -148,7 +186,7 @@ async function updateApiToken(entity, sourceId) {
|
||||
const apiTokenId = await sql.getRow("SELECT * FROM api_tokens WHERE apiTokenId = ?", [entity.apiTokenId]);
|
||||
|
||||
if (!apiTokenId) {
|
||||
await sql.doInTransaction(async () => {
|
||||
await sql.transactional(async () => {
|
||||
await sql.replace("api_tokens", entity);
|
||||
|
||||
await syncTableService.addApiTokenSync(entity.apiTokenId, sourceId);
|
||||
@@ -159,14 +197,5 @@ async function updateApiToken(entity, sourceId) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
updateNote,
|
||||
updateBranch,
|
||||
updateNoteRevision,
|
||||
updateNoteReordering,
|
||||
updateOptions,
|
||||
updateRecentNotes,
|
||||
updateImage,
|
||||
updateNoteImage,
|
||||
updateLabel,
|
||||
updateApiToken
|
||||
updateEntity
|
||||
};
|
||||
@@ -77,7 +77,7 @@ async function loadSubTreeNoteIds(parentNoteId, subTreeNoteIds) {
|
||||
}
|
||||
|
||||
async function sortNotesAlphabetically(parentNoteId) {
|
||||
await sql.doInTransaction(async () => {
|
||||
await sql.transactional(async () => {
|
||||
const notes = await sql.getRows(`SELECT branchId, noteId, title, isProtected
|
||||
FROM notes JOIN branches USING(noteId)
|
||||
WHERE branches.isDeleted = 0 AND parentNoteId = ?`, [parentNoteId]);
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hide-toggle" style="grid-area: tree-actions;">
|
||||
<div style="grid-area: left-pane; display: flex; flex-direction: column;" class="hide-toggle">
|
||||
<div style="display: flex; justify-content: space-around; padding: 10px 0 10px 0; margin: 0 20px 0 20px; border: 1px solid #ccc;">
|
||||
<a id="create-top-level-note-button" title="Create new top level note" class="icon-action"
|
||||
style="background: url('/images/icons/file-plus.png')"></a>
|
||||
@@ -54,28 +54,28 @@
|
||||
</div>
|
||||
|
||||
<input type="file" id="import-upload" style="display: none" />
|
||||
</div>
|
||||
|
||||
<div id="search-box" class="hide-toggle" style="grid-area: search; display: none; padding: 10px; margin-top: 10px;">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<input name="search-text" placeholder="Search text, labels" style="flex-grow: 100; margin-left: 5px; margin-right: 5px;" autocomplete="off">
|
||||
<button id="do-search-button" class="btn btn-primary btn-sm" title="Search">Search</button>
|
||||
<div id="search-box" style="display: none; padding: 10px; margin-top: 10px;">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<input name="search-text" placeholder="Search text, labels" style="flex-grow: 100; margin-left: 5px; margin-right: 5px;" autocomplete="off">
|
||||
<button id="do-search-button" class="btn btn-primary btn-sm" title="Search">Search</button>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; justify-content: space-evenly; margin-top: 10px;">
|
||||
<button id="reset-search-button" class="btn btn-sm" title="Reset search">Reset search</button>
|
||||
|
||||
<button id="save-search-button" class="btn btn-sm" title="Save search">Save search</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; justify-content: space-evenly; margin-top: 10px;">
|
||||
<button id="reset-search-button" class="btn btn-sm" title="Reset search">Reset search</button>
|
||||
|
||||
<button id="save-search-button" class="btn btn-sm" title="Save search">Save search</button>
|
||||
<div id="tree" class="hide-toggle" style="overflow: auto; flex-grow: 100; flex-shrink: 100; margin-top: 10px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tree" class="hide-toggle" style="grid-area: tree; overflow: auto;">
|
||||
</div>
|
||||
<div id="parent-list">
|
||||
<p><strong>Note locations:</strong></p>
|
||||
|
||||
<div id="parent-list" class="hide-toggle">
|
||||
<p><strong>Note locations:</strong></p>
|
||||
|
||||
<ul id="parent-list-inner"></ul>
|
||||
<ul id="parent-list-inner"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hide-toggle" style="grid-area: title;">
|
||||
@@ -132,82 +132,86 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="position: relative; overflow: auto; grid-area: note-content; padding-left: 10px; padding-top: 10px;" id="note-detail-wrapper">
|
||||
<div id="note-detail-text" class="note-detail-component"></div>
|
||||
<div id="note-detail-wrapper">
|
||||
<div id="note-detail-component-wrapper">
|
||||
<div id="note-detail-text" class="note-detail-component" tabindex="2"></div>
|
||||
|
||||
<div id="note-detail-search" class="note-detail-component">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<strong>Search string: </strong>
|
||||
<textarea rows="4" cols="50" id="search-string"></textarea>
|
||||
<div id="note-detail-search" class="note-detail-component">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<strong>Search string: </strong>
|
||||
<textarea rows="4" cols="50" id="search-string"></textarea>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<h4>Help</h4>
|
||||
<p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>@abc</code> - matches notes with label abc</li>
|
||||
<li>
|
||||
<code>@!abc</code> - matches notes without abc label (maybe not the best syntax)</li>
|
||||
<li>
|
||||
<code>@abc=true</code> - matches notes with label abc having value true</li>
|
||||
<li><code>@abc!=true</code></li>
|
||||
<li>
|
||||
<code>@"weird label"="weird value"</code> - works also with whitespace inside names values</li>
|
||||
<li>
|
||||
<code>@abc and @def</code> - matches notes with both abc and def</li>
|
||||
<li>
|
||||
<code>@abc @def</code> - AND relation is implicit when specifying multiple labels</li>
|
||||
<li>
|
||||
<code>@abc or @def</code> - OR relation</li>
|
||||
<li>
|
||||
<code>@abc<=5</code> - numerical comparison (also >, >=, <).</li>
|
||||
<li>
|
||||
<code>some search string @abc @def</code> - combination of fulltext and label search - both of them need to match (OR not supported)</li>
|
||||
<li>
|
||||
<code>@abc @def some search string</code> - same combination</li>
|
||||
</ul>
|
||||
|
||||
<a href="https://github.com/zadam/trilium/wiki/Labels">Complete help on search syntax</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<div id="note-detail-code" class="note-detail-component"></div>
|
||||
|
||||
<h4>Help</h4>
|
||||
<p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>@abc</code> - matches notes with label abc</li>
|
||||
<li>
|
||||
<code>@!abc</code> - matches notes without abc label (maybe not the best syntax)</li>
|
||||
<li>
|
||||
<code>@abc=true</code> - matches notes with label abc having value true</li>
|
||||
<li><code>@abc!=true</code></li>
|
||||
<li>
|
||||
<code>@"weird label"="weird value"</code> - works also with whitespace inside names values</li>
|
||||
<li>
|
||||
<code>@abc and @def</code> - matches notes with both abc and def</li>
|
||||
<li>
|
||||
<code>@abc @def</code> - AND relation is implicit when specifying multiple labels</li>
|
||||
<li>
|
||||
<code>@abc or @def</code> - OR relation</li>
|
||||
<li>
|
||||
<code>@abc<=5</code> - numerical comparison (also >, >=, <).</li>
|
||||
<li>
|
||||
<code>some search string @abc @def</code> - combination of fulltext and label search - both of them need to match (OR not supported)</li>
|
||||
<li>
|
||||
<code>@abc @def some search string</code> - same combination</li>
|
||||
</ul>
|
||||
<div id="note-detail-render" class="note-detail-component"></div>
|
||||
|
||||
<a href="https://github.com/zadam/trilium/wiki/Labels">Complete help on search syntax</a>
|
||||
</p>
|
||||
<div id="note-detail-file" class="note-detail-component">
|
||||
<table id="file-table">
|
||||
<tr>
|
||||
<th>File name:</th>
|
||||
<td id="file-filename"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>File type:</th>
|
||||
<td id="file-filetype"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>File size:</th>
|
||||
<td id="file-filesize"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<button id="file-download" class="btn btn-primary" type="button">Download</button>
|
||||
|
||||
<button id="file-open" class="btn btn-primary" type="button">Open</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<input type="file" id="file-upload" style="display: none" />
|
||||
</div>
|
||||
|
||||
<div id="note-detail-code" class="note-detail-component"></div>
|
||||
<div id="children-overview"></div>
|
||||
|
||||
<div id="note-detail-render" class="note-detail-component"></div>
|
||||
<div id="label-list">
|
||||
<button class="btn btn-sm show-labels-button">Labels:</button>
|
||||
|
||||
<div id="note-detail-file" class="note-detail-component">
|
||||
<table id="file-table">
|
||||
<tr>
|
||||
<th>File name:</th>
|
||||
<td id="file-filename"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>File type:</th>
|
||||
<td id="file-filetype"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>File size:</th>
|
||||
<td id="file-filesize"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<button id="file-download" class="btn btn-primary" type="button">Download</button>
|
||||
|
||||
<button id="file-open" class="btn btn-primary" type="button">Open</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<span id="label-list-inner"></span>
|
||||
</div>
|
||||
|
||||
<input type="file" id="file-upload" style="display: none" />
|
||||
</div>
|
||||
|
||||
<div id="label-list">
|
||||
<button class="btn btn-sm show-labels-button">Labels:</button>
|
||||
|
||||
<span id="label-list-inner"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
4
src/www
4
src/www
@@ -18,8 +18,6 @@ const log = require('./services/log');
|
||||
const appInfo = require('./services/app_info');
|
||||
const messagingService = require('./services/messaging');
|
||||
const utils = require('./services/utils');
|
||||
const sql = require('./services/sql');
|
||||
const sqlInit = require('./services/sql_init');
|
||||
|
||||
const port = normalizePort(config['Network']['port'] || '3000');
|
||||
app.set('port', port);
|
||||
@@ -56,7 +54,7 @@ httpServer.listen(port);
|
||||
httpServer.on('error', onError);
|
||||
httpServer.on('listening', onListening);
|
||||
|
||||
sqlInit.dbReady.then(() => messagingService.init(httpServer, sessionParser));
|
||||
messagingService.init(httpServer, sessionParser);
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const electronRouting = require('./routes/electron');
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/public" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
|
||||
Reference in New Issue
Block a user