mirror of
https://github.com/zadam/trilium.git
synced 2025-10-27 16:26:31 +01:00
Compare commits
36 Commits
v0.10.2-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 |
@@ -1,7 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<dataSource name="document.db">
|
<dataSource name="document.db">
|
||||||
<database-model serializer="dbm" rdbms="SQLITE" format-version="4.7">
|
<database-model serializer="dbm" rdbms="SQLITE" format-version="4.8">
|
||||||
<root id="1"/>
|
<root id="1">
|
||||||
|
<ServerVersion>3.16.1</ServerVersion>
|
||||||
|
</root>
|
||||||
<schema id="2" parent="1" name="main">
|
<schema id="2" parent="1" name="main">
|
||||||
<Current>1</Current>
|
<Current>1</Current>
|
||||||
<Visible>1</Visible>
|
<Visible>1</Visible>
|
||||||
@@ -107,8 +109,7 @@
|
|||||||
<index id="35" parent="7" name="IDX_branches_noteId_parentNoteId">
|
<index id="35" parent="7" name="IDX_branches_noteId_parentNoteId">
|
||||||
<ColNames>noteId
|
<ColNames>noteId
|
||||||
parentNoteId</ColNames>
|
parentNoteId</ColNames>
|
||||||
<ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
</ColumnCollations>
|
|
||||||
</index>
|
</index>
|
||||||
<index id="36" parent="7" name="IDX_branches_noteId">
|
<index id="36" parent="7" name="IDX_branches_noteId">
|
||||||
<ColNames>noteId</ColNames>
|
<ColNames>noteId</ColNames>
|
||||||
@@ -142,445 +143,449 @@ parentNoteId</ColNames>
|
|||||||
<ColNames>id</ColNames>
|
<ColNames>id</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
</key>
|
</key>
|
||||||
<foreign-key id="43" parent="8">
|
<column id="43" parent="9" name="imageId">
|
||||||
<ColNames>noteId</ColNames>
|
|
||||||
<RefTableName>notes</RefTableName>
|
|
||||||
<RefColNames>noteId</RefColNames>
|
|
||||||
</foreign-key>
|
|
||||||
<column id="44" parent="9" name="imageId">
|
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="45" parent="9" name="format">
|
<column id="44" parent="9" name="format">
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="46" parent="9" name="checksum">
|
<column id="45" parent="9" name="checksum">
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="47" parent="9" name="name">
|
<column id="46" parent="9" name="name">
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="48" parent="9" name="data">
|
<column id="47" parent="9" name="data">
|
||||||
<Position>5</Position>
|
<Position>5</Position>
|
||||||
<DataType>BLOB|0s</DataType>
|
<DataType>BLOB|0s</DataType>
|
||||||
</column>
|
</column>
|
||||||
<column id="49" parent="9" name="isDeleted">
|
<column id="48" parent="9" name="isDeleted">
|
||||||
<Position>6</Position>
|
<Position>6</Position>
|
||||||
<DataType>INT|0s</DataType>
|
<DataType>INT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<DefaultExpression>0</DefaultExpression>
|
<DefaultExpression>0</DefaultExpression>
|
||||||
</column>
|
</column>
|
||||||
<column id="50" parent="9" name="dateModified">
|
<column id="49" parent="9" name="dateModified">
|
||||||
<Position>7</Position>
|
<Position>7</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="51" parent="9" name="dateCreated">
|
<column id="50" parent="9" name="dateCreated">
|
||||||
<Position>8</Position>
|
<Position>8</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<index id="52" parent="9" name="sqlite_autoindex_images_1">
|
<index id="51" parent="9" name="sqlite_autoindex_images_1">
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>imageId</ColNames>
|
<ColNames>imageId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<key id="53" parent="9">
|
<key id="52" parent="9">
|
||||||
<ColNames>imageId</ColNames>
|
<ColNames>imageId</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<column id="54" parent="10" name="labelId">
|
<column id="53" parent="10" name="labelId">
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="55" parent="10" name="noteId">
|
<column id="54" parent="10" name="noteId">
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="56" parent="10" name="name">
|
<column id="55" parent="10" name="name">
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="57" parent="10" name="value">
|
<column id="56" parent="10" name="value">
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<DefaultExpression>''</DefaultExpression>
|
<DefaultExpression>''</DefaultExpression>
|
||||||
</column>
|
</column>
|
||||||
<column id="58" parent="10" name="position">
|
<column id="57" parent="10" name="position">
|
||||||
<Position>5</Position>
|
<Position>5</Position>
|
||||||
<DataType>INT|0s</DataType>
|
<DataType>INT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<DefaultExpression>0</DefaultExpression>
|
<DefaultExpression>0</DefaultExpression>
|
||||||
</column>
|
</column>
|
||||||
<column id="59" parent="10" name="dateCreated">
|
<column id="58" parent="10" name="dateCreated">
|
||||||
<Position>6</Position>
|
<Position>6</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="60" parent="10" name="dateModified">
|
<column id="59" parent="10" name="dateModified">
|
||||||
<Position>7</Position>
|
<Position>7</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="61" parent="10" name="isDeleted">
|
<column id="60" parent="10" name="isDeleted">
|
||||||
<Position>8</Position>
|
<Position>8</Position>
|
||||||
<DataType>INT|0s</DataType>
|
<DataType>INT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<index id="62" parent="10" name="sqlite_autoindex_labels_1">
|
<index id="61" parent="10" name="sqlite_autoindex_labels_1">
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>labelId</ColNames>
|
<ColNames>labelId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<index id="63" parent="10" name="IDX_labels_noteId">
|
<index id="62" parent="10" name="IDX_labels_noteId">
|
||||||
<ColNames>noteId</ColNames>
|
<ColNames>noteId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
</index>
|
</index>
|
||||||
<index id="64" parent="10" name="IDX_labels_name_value">
|
<index id="63" parent="10" name="IDX_labels_name_value">
|
||||||
<ColNames>name
|
<ColNames>name
|
||||||
value</ColNames>
|
value</ColNames>
|
||||||
<ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
</ColumnCollations>
|
|
||||||
</index>
|
</index>
|
||||||
<key id="65" parent="10">
|
<key id="64" parent="10">
|
||||||
<ColNames>labelId</ColNames>
|
<ColNames>labelId</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<column id="66" parent="11" name="noteImageId">
|
<column id="65" parent="11" name="noteImageId">
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="67" parent="11" name="noteId">
|
<column id="66" parent="11" name="noteId">
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="68" parent="11" name="imageId">
|
<column id="67" parent="11" name="imageId">
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="69" parent="11" name="isDeleted">
|
<column id="68" parent="11" name="isDeleted">
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
<DataType>INT|0s</DataType>
|
<DataType>INT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<DefaultExpression>0</DefaultExpression>
|
<DefaultExpression>0</DefaultExpression>
|
||||||
</column>
|
</column>
|
||||||
<column id="70" parent="11" name="dateModified">
|
<column id="69" parent="11" name="dateModified">
|
||||||
<Position>5</Position>
|
<Position>5</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="71" parent="11" name="dateCreated">
|
<column id="70" parent="11" name="dateCreated">
|
||||||
<Position>6</Position>
|
<Position>6</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</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>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>noteImageId</ColNames>
|
<ColNames>noteImageId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</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
|
<ColNames>noteId
|
||||||
imageId</ColNames>
|
imageId</ColNames>
|
||||||
<ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
</ColumnCollations>
|
|
||||||
</index>
|
</index>
|
||||||
<index id="74" parent="11" name="IDX_note_images_noteId">
|
<index id="73" parent="11" name="IDX_note_images_noteId">
|
||||||
<ColNames>noteId</ColNames>
|
<ColNames>noteId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
</index>
|
</index>
|
||||||
<index id="75" parent="11" name="IDX_note_images_imageId">
|
<index id="74" parent="11" name="IDX_note_images_imageId">
|
||||||
<ColNames>imageId</ColNames>
|
<ColNames>imageId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
</index>
|
</index>
|
||||||
<key id="76" parent="11">
|
<key id="75" parent="11">
|
||||||
<ColNames>noteImageId</ColNames>
|
<ColNames>noteImageId</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_note_images_1</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_note_images_1</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<column id="77" parent="12" name="noteRevisionId">
|
<column id="76" parent="12" name="noteRevisionId">
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="78" parent="12" name="noteId">
|
<column id="77" parent="12" name="noteId">
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="79" parent="12" name="title">
|
<column id="78" parent="12" name="title">
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
</column>
|
</column>
|
||||||
<column id="80" parent="12" name="content">
|
<column id="79" parent="12" name="content">
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
</column>
|
</column>
|
||||||
<column id="81" parent="12" name="isProtected">
|
<column id="80" parent="12" name="isProtected">
|
||||||
<Position>5</Position>
|
<Position>5</Position>
|
||||||
<DataType>INT|0s</DataType>
|
<DataType>INT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<DefaultExpression>0</DefaultExpression>
|
<DefaultExpression>0</DefaultExpression>
|
||||||
</column>
|
</column>
|
||||||
<column id="82" parent="12" name="dateModifiedFrom">
|
<column id="81" parent="12" name="dateModifiedFrom">
|
||||||
<Position>6</Position>
|
<Position>6</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="83" parent="12" name="dateModifiedTo">
|
<column id="82" parent="12" name="dateModifiedTo">
|
||||||
<Position>7</Position>
|
<Position>7</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</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>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>noteRevisionId</ColNames>
|
<ColNames>noteRevisionId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<index id="85" parent="12" name="IDX_note_revisions_noteId">
|
<index id="86" parent="12" name="IDX_note_revisions_noteId">
|
||||||
<ColNames>noteId</ColNames>
|
<ColNames>noteId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
</index>
|
</index>
|
||||||
<index id="86" parent="12" name="IDX_note_revisions_dateModifiedFrom">
|
<index id="87" parent="12" name="IDX_note_revisions_dateModifiedFrom">
|
||||||
<ColNames>dateModifiedFrom</ColNames>
|
<ColNames>dateModifiedFrom</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
</index>
|
</index>
|
||||||
<index id="87" parent="12" name="IDX_note_revisions_dateModifiedTo">
|
<index id="88" parent="12" name="IDX_note_revisions_dateModifiedTo">
|
||||||
<ColNames>dateModifiedTo</ColNames>
|
<ColNames>dateModifiedTo</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
</index>
|
</index>
|
||||||
<key id="88" parent="12">
|
<key id="89" parent="12">
|
||||||
<ColNames>noteRevisionId</ColNames>
|
<ColNames>noteRevisionId</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<column id="89" parent="13" name="noteId">
|
<column id="90" parent="13" name="noteId">
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="90" parent="13" name="title">
|
<column id="91" parent="13" name="title">
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
</column>
|
</column>
|
||||||
<column id="91" parent="13" name="content">
|
<column id="92" parent="13" name="content">
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
</column>
|
</column>
|
||||||
<column id="92" parent="13" name="isProtected">
|
<column id="93" parent="13" name="isProtected">
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
<DataType>INT|0s</DataType>
|
<DataType>INT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<DefaultExpression>0</DefaultExpression>
|
<DefaultExpression>0</DefaultExpression>
|
||||||
</column>
|
</column>
|
||||||
<column id="93" parent="13" name="isDeleted">
|
<column id="94" parent="13" name="isDeleted">
|
||||||
<Position>5</Position>
|
<Position>5</Position>
|
||||||
<DataType>INT|0s</DataType>
|
<DataType>INT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<DefaultExpression>0</DefaultExpression>
|
<DefaultExpression>0</DefaultExpression>
|
||||||
</column>
|
</column>
|
||||||
<column id="94" parent="13" name="dateCreated">
|
<column id="95" parent="13" name="dateCreated">
|
||||||
<Position>6</Position>
|
<Position>6</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="95" parent="13" name="dateModified">
|
<column id="96" parent="13" name="dateModified">
|
||||||
<Position>7</Position>
|
<Position>7</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="96" parent="13" name="type">
|
<column id="97" parent="13" name="type">
|
||||||
<Position>8</Position>
|
<Position>8</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<DefaultExpression>'text'</DefaultExpression>
|
<DefaultExpression>'text'</DefaultExpression>
|
||||||
</column>
|
</column>
|
||||||
<column id="97" parent="13" name="mime">
|
<column id="98" parent="13" name="mime">
|
||||||
<Position>9</Position>
|
<Position>9</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<DefaultExpression>'text/html'</DefaultExpression>
|
<DefaultExpression>'text/html'</DefaultExpression>
|
||||||
</column>
|
</column>
|
||||||
<index id="98" parent="13" name="sqlite_autoindex_notes_1">
|
<index id="99" parent="13" name="sqlite_autoindex_notes_1">
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>noteId</ColNames>
|
<ColNames>noteId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<index id="99" parent="13" name="IDX_notes_isDeleted">
|
<index id="100" parent="13" name="IDX_notes_isDeleted">
|
||||||
<ColNames>isDeleted</ColNames>
|
<ColNames>isDeleted</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
</index>
|
</index>
|
||||||
<key id="100" parent="13">
|
<key id="101" parent="13">
|
||||||
<ColNames>noteId</ColNames>
|
<ColNames>noteId</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<column id="101" parent="14" name="name">
|
<column id="102" parent="14" name="name">
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="102" parent="14" name="value">
|
<column id="103" parent="14" name="value">
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
</column>
|
</column>
|
||||||
<column id="103" parent="14" name="dateModified">
|
<column id="104" parent="14" name="dateModified">
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
<DataType>INT|0s</DataType>
|
<DataType>INT|0s</DataType>
|
||||||
</column>
|
</column>
|
||||||
<column id="104" parent="14" name="isSynced">
|
<column id="105" parent="14" name="isSynced">
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
<DataType>INTEGER|0s</DataType>
|
<DataType>INTEGER|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<DefaultExpression>0</DefaultExpression>
|
<DefaultExpression>0</DefaultExpression>
|
||||||
</column>
|
</column>
|
||||||
<index id="105" parent="14" name="sqlite_autoindex_options_1">
|
<index id="106" parent="14" name="sqlite_autoindex_options_1">
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>name</ColNames>
|
<ColNames>name</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<key id="106" parent="14">
|
<key id="107" parent="14">
|
||||||
<ColNames>name</ColNames>
|
<ColNames>name</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<column id="107" parent="15" name="branchId">
|
<column id="108" parent="15" name="branchId">
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="108" parent="15" name="notePath">
|
<column id="109" parent="15" name="notePath">
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="109" parent="15" name="dateAccessed">
|
<column id="110" parent="15" name="dateAccessed">
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="110" parent="15" name="isDeleted">
|
<column id="111" parent="15" name="isDeleted">
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
<DataType>INT|0s</DataType>
|
<DataType>INT|0s</DataType>
|
||||||
</column>
|
</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>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>branchId</ColNames>
|
<ColNames>branchId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<key id="112" parent="15">
|
<key id="113" parent="15">
|
||||||
<ColNames>branchId</ColNames>
|
<ColNames>branchId</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<column id="113" parent="16" name="sourceId">
|
<column id="114" parent="16" name="sourceId">
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="114" parent="16" name="dateCreated">
|
<column id="115" parent="16" name="dateCreated">
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</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>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>sourceId</ColNames>
|
<ColNames>sourceId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<key id="116" parent="16">
|
<key id="117" parent="16">
|
||||||
<ColNames>sourceId</ColNames>
|
<ColNames>sourceId</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<column id="117" parent="17" name="type">
|
<column id="118" parent="17" name="type">
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
<DataType>text|0s</DataType>
|
<DataType>text|0s</DataType>
|
||||||
</column>
|
</column>
|
||||||
<column id="118" parent="17" name="name">
|
<column id="119" parent="17" name="name">
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
<DataType>text|0s</DataType>
|
<DataType>text|0s</DataType>
|
||||||
</column>
|
</column>
|
||||||
<column id="119" parent="17" name="tbl_name">
|
<column id="120" parent="17" name="tbl_name">
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
<DataType>text|0s</DataType>
|
<DataType>text|0s</DataType>
|
||||||
</column>
|
</column>
|
||||||
<column id="120" parent="17" name="rootpage">
|
<column id="121" parent="17" name="rootpage">
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
<DataType>integer|0s</DataType>
|
<DataType>integer|0s</DataType>
|
||||||
</column>
|
</column>
|
||||||
<column id="121" parent="17" name="sql">
|
<column id="122" parent="17" name="sql">
|
||||||
<Position>5</Position>
|
<Position>5</Position>
|
||||||
<DataType>text|0s</DataType>
|
<DataType>text|0s</DataType>
|
||||||
</column>
|
</column>
|
||||||
<column id="122" parent="18" name="name">
|
<column id="123" parent="18" name="name">
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="123" parent="18" name="seq">
|
<column id="124" parent="18" name="seq">
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="124" parent="19" name="id">
|
<column id="125" parent="19" name="id">
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
<DataType>INTEGER|0s</DataType>
|
<DataType>INTEGER|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<SequenceIdentity>1</SequenceIdentity>
|
<SequenceIdentity>1</SequenceIdentity>
|
||||||
</column>
|
</column>
|
||||||
<column id="125" parent="19" name="entityName">
|
<column id="126" parent="19" name="entityName">
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="126" parent="19" name="entityId">
|
<column id="127" parent="19" name="entityId">
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="127" parent="19" name="sourceId">
|
<column id="128" parent="19" name="sourceId">
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="128" parent="19" name="syncDate">
|
<column id="129" parent="19" name="syncDate">
|
||||||
<Position>5</Position>
|
<Position>5</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<index id="129" parent="19" name="IDX_sync_entityName_entityId">
|
<index id="130" parent="19" name="IDX_sync_entityName_entityId">
|
||||||
<ColNames>entityName
|
<ColNames>entityName
|
||||||
entityId</ColNames>
|
entityId</ColNames>
|
||||||
<ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
</ColumnCollations>
|
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<index id="130" parent="19" name="IDX_sync_syncDate">
|
<index id="131" parent="19" name="IDX_sync_syncDate">
|
||||||
<ColNames>syncDate</ColNames>
|
<ColNames>syncDate</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
<ColumnCollations></ColumnCollations>
|
||||||
</index>
|
</index>
|
||||||
<key id="131" parent="19">
|
<key id="132" parent="19">
|
||||||
<ColNames>id</ColNames>
|
<ColNames>id</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
</key>
|
</key>
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ jq '.version = "'$VERSION'"' package.json|sponge package.json
|
|||||||
|
|
||||||
git add 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
|
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,
|
`dateCreated` TEXT NOT NULL,
|
||||||
PRIMARY KEY(`sourceId`)
|
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" (
|
CREATE TABLE IF NOT EXISTS "note_revisions" (
|
||||||
`noteRevisionId` TEXT NOT NULL PRIMARY KEY,
|
`noteRevisionId` TEXT NOT NULL PRIMARY KEY,
|
||||||
`noteId` TEXT NOT NULL,
|
`noteId` TEXT NOT NULL,
|
||||||
@@ -51,7 +29,7 @@ CREATE TABLE IF NOT EXISTS "note_revisions" (
|
|||||||
`isProtected` INT NOT NULL DEFAULT 0,
|
`isProtected` INT NOT NULL DEFAULT 0,
|
||||||
`dateModifiedFrom` TEXT NOT NULL,
|
`dateModifiedFrom` TEXT NOT NULL,
|
||||||
`dateModifiedTo` 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` (
|
CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` (
|
||||||
`noteId`
|
`noteId`
|
||||||
);
|
);
|
||||||
@@ -130,3 +108,25 @@ CREATE INDEX IDX_labels_name_value
|
|||||||
on labels (name, value);
|
on labels (name, value);
|
||||||
CREATE INDEX IDX_labels_noteId
|
CREATE INDEX IDX_labels_noteId
|
||||||
on 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`
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"description": "Trilium Notes",
|
"description": "Trilium Notes",
|
||||||
"version": "0.10.2-beta",
|
"version": "0.12.0",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -12,13 +12,21 @@ class Note extends Entity {
|
|||||||
constructor(row) {
|
constructor(row) {
|
||||||
super(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);
|
protected_session.decryptNote(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isJson()) {
|
this.setContent(this.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
setContent(content) {
|
||||||
|
this.content = content;
|
||||||
|
|
||||||
|
try {
|
||||||
this.jsonContent = JSON.parse(this.content);
|
this.jsonContent = JSON.parse(this.content);
|
||||||
}
|
}
|
||||||
|
catch(e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
isJson() {
|
isJson() {
|
||||||
@@ -133,7 +141,7 @@ class Note extends Entity {
|
|||||||
beforeSaving() {
|
beforeSaving() {
|
||||||
super.beforeSaving();
|
super.beforeSaving();
|
||||||
|
|
||||||
if (this.isJson()) {
|
if (this.isJson() && this.jsonContent) {
|
||||||
this.content = JSON.stringify(this.jsonContent, null, '\t');
|
this.content = JSON.stringify(this.jsonContent, null, '\t');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ async function showDialog() {
|
|||||||
$clonePrefix.val('');
|
$clonePrefix.val('');
|
||||||
$linkTitle.val('');
|
$linkTitle.val('');
|
||||||
|
|
||||||
function setDefaultLinkTitle(noteId) {
|
async function setDefaultLinkTitle(noteId) {
|
||||||
const noteTitle = treeUtils.getNoteTitle(noteId);
|
const noteTitle = await treeUtils.getNoteTitle(noteId);
|
||||||
|
|
||||||
$linkTitle.val(noteTitle);
|
$linkTitle.val(noteTitle);
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ async function showDialog() {
|
|||||||
$autoComplete.autocomplete({
|
$autoComplete.autocomplete({
|
||||||
source: await autocompleteService.getAutocompleteItems(),
|
source: await autocompleteService.getAutocompleteItems(),
|
||||||
minLength: 0,
|
minLength: 0,
|
||||||
change: () => {
|
change: async () => {
|
||||||
const val = $autoComplete.val();
|
const val = $autoComplete.val();
|
||||||
const notePath = linkService.getNodePathFromLabel(val);
|
const notePath = linkService.getNodePathFromLabel(val);
|
||||||
if (!notePath) {
|
if (!notePath) {
|
||||||
@@ -64,16 +64,16 @@ async function showDialog() {
|
|||||||
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
||||||
|
|
||||||
if (noteId) {
|
if (noteId) {
|
||||||
setDefaultLinkTitle(noteId);
|
await setDefaultLinkTitle(noteId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// this is called when user goes through autocomplete list with keyboard
|
// 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
|
// 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 notePath = linkService.getNodePathFromLabel(ui.item.value);
|
||||||
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
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);
|
const revisionItem = revisionItems.find(r => r.noteRevisionId === optVal);
|
||||||
|
|
||||||
$title.html(revisionItem.title);
|
$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 => {
|
$(document).on('click', "a[action='note-revision']", event => {
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ class Branch {
|
|||||||
return await this.treeCache.getNote(this.noteId);
|
return await this.treeCache.getNote(this.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isTopLevel() {
|
||||||
|
return this.parentNoteId === 'root';
|
||||||
|
}
|
||||||
|
|
||||||
get toString() {
|
get toString() {
|
||||||
return `Branch(branchId=${this.branchId})`;
|
return `Branch(branchId=${this.branchId})`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ class NoteFull extends NoteShort {
|
|||||||
|
|
||||||
this.content = row.content;
|
this.content = row.content;
|
||||||
|
|
||||||
if (this.isJson()) {
|
if (this.content !== "" && this.isJson()) {
|
||||||
this.jsonContent = JSON.parse(this.content);
|
try {
|
||||||
|
this.jsonContent = JSON.parse(this.content);
|
||||||
|
}
|
||||||
|
catch(e) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ class NoteShort {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getChildBranches() {
|
async getChildBranches() {
|
||||||
|
if (!this.treeCache.children[this.noteId]) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const branches = [];
|
const branches = [];
|
||||||
|
|
||||||
for (const child of this.treeCache.children[this.noteId]) {
|
for (const child of this.treeCache.children[this.noteId]) {
|
||||||
@@ -44,6 +48,14 @@ class NoteShort {
|
|||||||
get toString() {
|
get toString() {
|
||||||
return `Note(noteId=${this.noteId}, title=${this.title})`;
|
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;
|
export default NoteShort;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import treeCache from "./tree_cache.js";
|
import treeCache from "./tree_cache.js";
|
||||||
import treeUtils from "./tree_utils.js";
|
import treeUtils from "./tree_utils.js";
|
||||||
|
import protectedSessionHolder from './protected_session_holder.js';
|
||||||
|
|
||||||
async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
|
async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
|
||||||
if (!parentNoteId) {
|
if (!parentNoteId) {
|
||||||
@@ -21,9 +22,6 @@ async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
|
|||||||
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 = [];
|
const autocompleteItems = [];
|
||||||
|
|
||||||
for (const childNote of childNotes) {
|
for (const childNote of childNotes) {
|
||||||
@@ -34,10 +32,12 @@ async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
|
|||||||
const childNotePath = (notePath ? (notePath + '/') : '') + childNote.noteId;
|
const childNotePath = (notePath ? (notePath + '/') : '') + childNote.noteId;
|
||||||
const childTitlePath = (titlePath ? (titlePath + ' / ') : '') + await treeUtils.getNoteTitle(childNote.noteId, parentNoteId);
|
const childTitlePath = (titlePath ? (titlePath + ' / ') : '') + await treeUtils.getNoteTitle(childNote.noteId, parentNoteId);
|
||||||
|
|
||||||
autocompleteItems.push({
|
if (!childNote.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||||
value: childTitlePath + ' (' + childNotePath + ')',
|
autocompleteItems.push({
|
||||||
label: childTitlePath
|
value: childTitlePath + ' (' + childNotePath + ')',
|
||||||
});
|
label: childTitlePath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const childItems = await getAutocompleteItems(childNote.noteId, childNotePath, childTitlePath);
|
const childItems = await getAutocompleteItems(childNote.noteId, childNotePath, childTitlePath);
|
||||||
|
|
||||||
|
|||||||
@@ -76,9 +76,11 @@ function goToLink(e) {
|
|||||||
|
|
||||||
function addLinkToEditor(linkTitle, linkHref) {
|
function addLinkToEditor(linkTitle, linkHref) {
|
||||||
const editor = noteDetailText.getEditor();
|
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) {
|
function addTextToEditor(text) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import treeService from './tree.js';
|
import treeService from './tree.js';
|
||||||
|
import treeUtils from './tree_utils.js';
|
||||||
import noteTypeService from './note_type.js';
|
import noteTypeService from './note_type.js';
|
||||||
import protectedSessionService from './protected_session.js';
|
import protectedSessionService from './protected_session.js';
|
||||||
import protectedSessionHolder from './protected_session_holder.js';
|
import protectedSessionHolder from './protected_session_holder.js';
|
||||||
@@ -24,6 +25,7 @@ const $noteDetailWrapper = $("#note-detail-wrapper");
|
|||||||
const $noteIdDisplay = $("#note-id-display");
|
const $noteIdDisplay = $("#note-id-display");
|
||||||
const $labelList = $("#label-list");
|
const $labelList = $("#label-list");
|
||||||
const $labelListInner = $("#label-list-inner");
|
const $labelListInner = $("#label-list-inner");
|
||||||
|
const $childrenOverview = $("#children-overview");
|
||||||
|
|
||||||
let currentNote = null;
|
let currentNote = null;
|
||||||
|
|
||||||
@@ -73,50 +75,42 @@ function noteChanged() {
|
|||||||
async function reload() {
|
async function reload() {
|
||||||
// no saving here
|
// no saving here
|
||||||
|
|
||||||
await loadNoteToEditor(getCurrentNoteId());
|
await loadNoteDetail(getCurrentNoteId());
|
||||||
}
|
}
|
||||||
|
|
||||||
async function switchToNote(noteId) {
|
async function switchToNote(noteId) {
|
||||||
if (getCurrentNoteId() !== noteId) {
|
if (getCurrentNoteId() !== noteId) {
|
||||||
await saveNoteIfChanged();
|
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() {
|
async function saveNoteIfChanged() {
|
||||||
if (!isNoteChanged) {
|
if (!isNoteChanged) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = getCurrentNote();
|
await saveNote();
|
||||||
|
|
||||||
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!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setNoteBackgroundIfProtected(note) {
|
function setNoteBackgroundIfProtected(note) {
|
||||||
@@ -145,7 +139,7 @@ async function handleProtectedSession() {
|
|||||||
protectedSessionService.ensureDialogIsClosed();
|
protectedSessionService.ensureDialogIsClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadNoteToEditor(noteId) {
|
async function loadNoteDetail(noteId) {
|
||||||
currentNote = await loadNote(noteId);
|
currentNote = await loadNote(noteId);
|
||||||
|
|
||||||
if (isNewNoteCreated) {
|
if (isNewNoteCreated) {
|
||||||
@@ -182,7 +176,35 @@ async function loadNoteToEditor(noteId) {
|
|||||||
// after loading new note make sure editor is scrolled to the top
|
// after loading new note make sure editor is scrolled to the top
|
||||||
$noteDetailWrapper.scrollTop(0);
|
$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() {
|
async function loadLabelList() {
|
||||||
@@ -202,6 +224,8 @@ async function loadLabelList() {
|
|||||||
else {
|
else {
|
||||||
$labelList.hide();
|
$labelList.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadNote(noteId) {
|
async function loadNote(noteId) {
|
||||||
@@ -245,8 +269,6 @@ setInterval(saveNoteIfChanged, 5000);
|
|||||||
export default {
|
export default {
|
||||||
reload,
|
reload,
|
||||||
switchToNote,
|
switchToNote,
|
||||||
updateNoteFromInputs,
|
|
||||||
saveNoteToServer,
|
|
||||||
setNoteBackgroundIfProtected,
|
setNoteBackgroundIfProtected,
|
||||||
loadNote,
|
loadNote,
|
||||||
getCurrentNote,
|
getCurrentNote,
|
||||||
@@ -255,6 +277,7 @@ export default {
|
|||||||
newNoteCreated,
|
newNoteCreated,
|
||||||
focus,
|
focus,
|
||||||
loadLabelList,
|
loadLabelList,
|
||||||
|
saveNote,
|
||||||
saveNoteIfChanged,
|
saveNoteIfChanged,
|
||||||
noteChanged
|
noteChanged
|
||||||
};
|
};
|
||||||
@@ -16,6 +16,10 @@ async function show() {
|
|||||||
CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
|
CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
|
||||||
CodeMirror.keyMap.default["Tab"] = "indentMore";
|
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';
|
CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js';
|
||||||
|
|
||||||
codeEditor = CodeMirror($noteDetailCode[0], {
|
codeEditor = CodeMirror($noteDetailCode[0], {
|
||||||
@@ -27,7 +31,8 @@ async function show() {
|
|||||||
highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false},
|
highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false},
|
||||||
lint: true,
|
lint: true,
|
||||||
gutters: ["CodeMirror-lint-markers"],
|
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);
|
codeEditor.on('change', noteDetailService.noteChanged);
|
||||||
|
|||||||
@@ -11,11 +11,10 @@ async function show() {
|
|||||||
|
|
||||||
textEditor = await BalloonEditor.create($noteDetailText[0], {});
|
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);
|
||||||
textEditor.setData(noteDetailService.getCurrentNote().content || "<p></p>");
|
|
||||||
|
|
||||||
$noteDetailText.show();
|
$noteDetailText.show();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import treeService from './tree.js';
|
import treeService from './tree.js';
|
||||||
import noteDetail from './note_detail.js';
|
import noteDetailService from './note_detail.js';
|
||||||
import server from './server.js';
|
import server from './server.js';
|
||||||
import infoService from "./info.js";
|
import infoService from "./info.js";
|
||||||
|
|
||||||
@@ -84,13 +84,13 @@ function NoteTypeModel() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
const note = noteDetail.getCurrentNote();
|
const note = noteDetailService.getCurrentNote();
|
||||||
|
|
||||||
await server.put('notes/' + note.noteId
|
await server.put('notes/' + note.noteId
|
||||||
+ '/type/' + encodeURIComponent(self.type())
|
+ '/type/' + encodeURIComponent(self.type())
|
||||||
+ '/mime/' + encodeURIComponent(self.mime()));
|
+ '/mime/' + encodeURIComponent(self.mime()));
|
||||||
|
|
||||||
await noteDetail.reload();
|
await noteDetailService.reload();
|
||||||
|
|
||||||
// for the note icon to be updated in the tree
|
// for the note icon to be updated in the tree
|
||||||
await treeService.reload();
|
await treeService.reload();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import treeService from './tree.js';
|
import treeService from './tree.js';
|
||||||
import noteDetail from './note_detail.js';
|
import noteDetailService from './note_detail.js';
|
||||||
import utils from './utils.js';
|
import utils from './utils.js';
|
||||||
import server from './server.js';
|
import server from './server.js';
|
||||||
import protectedSessionHolder from './protected_session_holder.js';
|
import protectedSessionHolder from './protected_session_holder.js';
|
||||||
@@ -57,7 +57,7 @@ async function setupProtectedSession() {
|
|||||||
|
|
||||||
$dialog.dialog("close");
|
$dialog.dialog("close");
|
||||||
|
|
||||||
noteDetail.reload();
|
noteDetailService.reload();
|
||||||
treeService.reload();
|
treeService.reload();
|
||||||
|
|
||||||
if (protectedSessionDeferred !== null) {
|
if (protectedSessionDeferred !== null) {
|
||||||
@@ -90,33 +90,27 @@ async function enterProtectedSession(password) {
|
|||||||
async function protectNoteAndSendToServer() {
|
async function protectNoteAndSendToServer() {
|
||||||
await ensureProtectedSession(true, true);
|
await ensureProtectedSession(true, true);
|
||||||
|
|
||||||
const note = noteDetail.getCurrentNote();
|
const note = noteDetailService.getCurrentNote();
|
||||||
|
|
||||||
noteDetail.updateNoteFromInputs(note);
|
|
||||||
|
|
||||||
note.isProtected = true;
|
note.isProtected = true;
|
||||||
|
|
||||||
await noteDetail.saveNoteToServer(note);
|
await noteDetailService.saveNote(note);
|
||||||
|
|
||||||
treeService.setProtected(note.noteId, note.isProtected);
|
treeService.setProtected(note.noteId, note.isProtected);
|
||||||
|
|
||||||
noteDetail.setNoteBackgroundIfProtected(note);
|
noteDetailService.setNoteBackgroundIfProtected(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unprotectNoteAndSendToServer() {
|
async function unprotectNoteAndSendToServer() {
|
||||||
await ensureProtectedSession(true, true);
|
await ensureProtectedSession(true, true);
|
||||||
|
|
||||||
const note = noteDetail.getCurrentNote();
|
const note = noteDetailService.getCurrentNote();
|
||||||
|
|
||||||
noteDetail.updateNoteFromInputs(note);
|
|
||||||
|
|
||||||
note.isProtected = false;
|
note.isProtected = false;
|
||||||
|
|
||||||
await noteDetail.saveNoteToServer(note);
|
await noteDetailService.saveNote(note);
|
||||||
|
|
||||||
treeService.setProtected(note.noteId, note.isProtected);
|
treeService.setProtected(note.noteId, note.isProtected);
|
||||||
|
|
||||||
noteDetail.setNoteBackgroundIfProtected(note);
|
noteDetailService.setNoteBackgroundIfProtected(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function protectBranch(noteId, protect) {
|
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");
|
infoService.showMessage("Request to un/protect sub tree has finished successfully");
|
||||||
|
|
||||||
treeService.reload();
|
treeService.reload();
|
||||||
noteDetail.reload();
|
noteDetailService.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
$passwordForm.submit(() => {
|
$passwordForm.submit(() => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import treeService from './tree.js';
|
import treeService from './tree.js';
|
||||||
import server from './server.js';
|
import server from './server.js';
|
||||||
import utils from './utils.js';
|
import utils from './utils.js';
|
||||||
|
import infoService from './info.js';
|
||||||
|
|
||||||
function ScriptApi(startNote, currentNote) {
|
function ScriptApi(startNote, currentNote) {
|
||||||
const $pluginButtons = $("#plugin-buttons");
|
const $pluginButtons = $("#plugin-buttons");
|
||||||
@@ -56,8 +57,8 @@ function ScriptApi(startNote, currentNote) {
|
|||||||
runOnServer,
|
runOnServer,
|
||||||
formatDateISO: utils.formatDateISO,
|
formatDateISO: utils.formatDateISO,
|
||||||
parseDate: utils.parseDate,
|
parseDate: utils.parseDate,
|
||||||
showMessage: utils.showMessage,
|
showMessage: infoService.showMessage,
|
||||||
showError: utils.showError,
|
showError: infoService.showError,
|
||||||
reloadTree: treeService.reload
|
reloadTree: treeService.reload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ function initFancyTree(branch) {
|
|||||||
keyboard: false, // we takover keyboard handling in the hotkeys plugin
|
keyboard: false, // we takover keyboard handling in the hotkeys plugin
|
||||||
extensions: ["hotkeys", "filter", "dnd", "clones"],
|
extensions: ["hotkeys", "filter", "dnd", "clones"],
|
||||||
source: branch,
|
source: branch,
|
||||||
scrollParent: $("#tree"),
|
scrollParent: $tree,
|
||||||
click: (event, data) => {
|
click: (event, data) => {
|
||||||
const targetType = data.targetType;
|
const targetType = data.targetType;
|
||||||
const node = data.node;
|
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
|
utils.bindShortcut('alt+c', () => collapseTree()); // don't use shortened form since collapseTree() accepts argument
|
||||||
|
$collapseTreeButton.click(() => collapseTree());
|
||||||
|
|
||||||
$createTopLevelNoteButton.click(createNewTopLevelNote);
|
$createTopLevelNoteButton.click(createNewTopLevelNote);
|
||||||
$collapseTreeButton.click(collapseTree);
|
|
||||||
$scrollToCurrentNoteButton.click(scrollToCurrentNote);
|
$scrollToCurrentNoteButton.click(scrollToCurrentNote);
|
||||||
|
|
||||||
export default {
|
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
|
* Helpers
|
||||||
*----------------------------------------------------------------------------*/
|
*----------------------------------------------------------------------------*/
|
||||||
.ui-helper-hidden {
|
.fancytree-helper-hidden {
|
||||||
display: none;
|
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
|
* Container and UL / LI
|
||||||
*----------------------------------------------------------------------------*/
|
*----------------------------------------------------------------------------*/
|
||||||
@@ -338,6 +369,16 @@ span.fancytree-node.fancytree-error span.fancytree-title {
|
|||||||
/*------------------------------------------------------------------------------
|
/*------------------------------------------------------------------------------
|
||||||
* Drag'n'drop support
|
* 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-childcounter,
|
||||||
div.fancytree-drag-helper span.fancytree-dnd-modifier {
|
div.fancytree-drag-helper span.fancytree-dnd-modifier {
|
||||||
display: inline-block;
|
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-connector,
|
||||||
.fancytree-container.fancytree-rtl span.fancytree-expander,
|
.fancytree-container.fancytree-rtl span.fancytree-expander,
|
||||||
.fancytree-container.fancytree-rtl span.fancytree-icon,
|
.fancytree-container.fancytree-rtl span.fancytree-icon,
|
||||||
.fancytree-container.fancytree-rtl span.fancytree-drag-helper-img,
|
.fancytree-container.fancytree-rtl span.fancytree-drag-helper-img {
|
||||||
.fancytree-container.fancytree-rtl #fancytree-drop-marker {
|
|
||||||
background-image: url("../skin-win8/icons-rtl.gif");
|
background-image: url("../skin-win8/icons-rtl.gif");
|
||||||
}
|
}
|
||||||
.fancytree-container.fancytree-rtl .fancytree-exp-n span.fancytree-expander,
|
.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 {
|
ul.fancytree-container.fancytree-rtl.fancytree-no-connector > li {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
}
|
}
|
||||||
|
#fancytree-drop-marker.fancytree-rtl {
|
||||||
|
background-image: url("../skin-win8/icons-rtl.gif");
|
||||||
|
}
|
||||||
/*------------------------------------------------------------------------------
|
/*------------------------------------------------------------------------------
|
||||||
* 'table' extension
|
* 'table' extension
|
||||||
*----------------------------------------------------------------------------*/
|
*----------------------------------------------------------------------------*/
|
||||||
@@ -482,7 +525,7 @@ table.fancytree-ext-columnview .fancytree-has-children span.fancytree-cv-right:h
|
|||||||
* 'filter' extension
|
* 'filter' extension
|
||||||
*----------------------------------------------------------------------------*/
|
*----------------------------------------------------------------------------*/
|
||||||
.fancytree-ext-filter-dimm span.fancytree-node span.fancytree-title {
|
.fancytree-ext-filter-dimm span.fancytree-node span.fancytree-title {
|
||||||
color: silver;
|
color: #c0c0c0;
|
||||||
font-weight: lighter;
|
font-weight: lighter;
|
||||||
}
|
}
|
||||||
.fancytree-ext-filter-dimm tr.fancytree-submatch span.fancytree-title,
|
.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 tr.fancytree-submatch span.fancytree-title,
|
||||||
.fancytree-ext-filter-hide span.fancytree-node.fancytree-submatch span.fancytree-title {
|
.fancytree-ext-filter-hide span.fancytree-node.fancytree-submatch span.fancytree-title {
|
||||||
color: silver;
|
color: #c0c0c0;
|
||||||
font-weight: lighter;
|
font-weight: lighter;
|
||||||
}
|
}
|
||||||
.fancytree-ext-filter-hide tr.fancytree-match span.fancytree-title,
|
.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
@@ -4,18 +4,12 @@
|
|||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas: "header header"
|
grid-template-areas: "header header"
|
||||||
"tree-actions title"
|
"left-pane title"
|
||||||
"search note-content"
|
"left-pane note-detail";
|
||||||
"tree note-content"
|
|
||||||
"parent-list note-content"
|
|
||||||
"parent-list label-list";
|
|
||||||
grid-template-columns: 2fr 5fr;
|
grid-template-columns: 2fr 5fr;
|
||||||
grid-template-rows: auto
|
grid-template-rows: auto
|
||||||
auto
|
auto
|
||||||
auto
|
1fr;
|
||||||
1fr
|
|
||||||
auto
|
|
||||||
auto;
|
|
||||||
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
grid-gap: 10px;
|
grid-gap: 10px;
|
||||||
@@ -28,6 +22,23 @@
|
|||||||
align-items: center;
|
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 {
|
.note-detail-component {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -36,7 +47,7 @@
|
|||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
/* This is because with empty content height of editor is 0 and it's impossible to click into it */
|
/* 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;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +260,7 @@ div.ui-tooltip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#note-detail-code {
|
#note-detail-code {
|
||||||
height: 100%;
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
@@ -274,7 +285,6 @@ div.ui-tooltip {
|
|||||||
.cm-matchhighlight {background-color: #eeeeee}
|
.cm-matchhighlight {background-color: #eeeeee}
|
||||||
|
|
||||||
#label-list {
|
#label-list {
|
||||||
grid-area: label-list;
|
|
||||||
color: #777777;
|
color: #777777;
|
||||||
border-top: 1px solid #eee;
|
border-top: 1px solid #eee;
|
||||||
padding: 5px; display: none;
|
padding: 5px; display: none;
|
||||||
@@ -288,4 +298,36 @@ div.ui-tooltip {
|
|||||||
#file-table th, #file-table td {
|
#file-table th, #file-table td {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: large;
|
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 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();
|
pack.finalize();
|
||||||
|
|
||||||
@@ -23,51 +84,6 @@ async function exportNote(req, res) {
|
|||||||
pack.pipe(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 = {
|
module.exports = {
|
||||||
exportNote
|
exportNote
|
||||||
};
|
};
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
const repository = require('../../services/repository');
|
const repository = require('../../services/repository');
|
||||||
const labelService = require('../../services/labels');
|
const labelService = require('../../services/labels');
|
||||||
const noteService = require('../../services/notes');
|
const noteService = require('../../services/notes');
|
||||||
|
const Branch = require('../../entities/branch');
|
||||||
const tar = require('tar-stream');
|
const tar = require('tar-stream');
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
@@ -31,7 +32,7 @@ async function parseImportFile(file) {
|
|||||||
const extract = tar.extract();
|
const extract = tar.extract();
|
||||||
|
|
||||||
extract.on('entry', function(header, stream, next) {
|
extract.on('entry', function(header, stream, next) {
|
||||||
let {name, key} = getFileName(header.name);
|
const {name, key} = getFileName(header.name);
|
||||||
|
|
||||||
let file = fileMap[name];
|
let file = fileMap[name];
|
||||||
|
|
||||||
@@ -97,30 +98,46 @@ async function importTar(req) {
|
|||||||
|
|
||||||
const files = await parseImportFile(file);
|
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) {
|
for (const file of files) {
|
||||||
if (file.meta.version !== 1) {
|
if (file.meta.version !== 1) {
|
||||||
throw new Error("Can't read meta data version " + file.meta.version);
|
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') {
|
if (file.meta.type !== 'file') {
|
||||||
file.data = file.data.toString("UTF-8");
|
file.data = file.data.toString("UTF-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
const {note} = await noteService.createNote(parentNoteId, file.meta.title, file.data, {
|
const {note} = await noteService.createNote(parentNoteId, file.meta.title, file.data, {
|
||||||
type: file.meta.type,
|
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) {
|
for (const label of file.meta.labels) {
|
||||||
await labelService.createLabel(note.noteId, label.name, label.value);
|
await labelService.createLabel(note.noteId, label.name, label.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.children.length > 0) {
|
if (file.children.length > 0) {
|
||||||
await importNotes(file.children, note.noteId);
|
await importNotes(file.children, note.noteId, noteIdMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ const log = require('../../services/log');
|
|||||||
|
|
||||||
async function checkSync() {
|
async function checkSync() {
|
||||||
return {
|
return {
|
||||||
'hashes': await contentHashService.getHashes(),
|
hashes: await contentHashService.getHashes(),
|
||||||
'max_sync_id': await sql.getValue('SELECT MAX(id) FROM sync')
|
maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,126 +58,18 @@ async function forceNoteSync(req) {
|
|||||||
async function getChanged(req) {
|
async function getChanged(req) {
|
||||||
const lastSyncId = parseInt(req.query.lastSyncId);
|
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) {
|
async function update(req) {
|
||||||
const noteId = req.params.noteId;
|
const sourceId = req.body.sourceId;
|
||||||
const entity = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
const entities = req.body.entities;
|
||||||
|
|
||||||
syncService.serializeNoteContentBuffer(entity);
|
for (const {sync, entity} of entities) {
|
||||||
|
await syncUpdateService.updateEntity(sync, entity, sourceId);
|
||||||
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."];
|
|
||||||
}
|
}
|
||||||
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 = {
|
module.exports = {
|
||||||
@@ -187,24 +79,5 @@ module.exports = {
|
|||||||
forceFullSync,
|
forceFullSync,
|
||||||
forceNoteSync,
|
forceNoteSync,
|
||||||
getChanged,
|
getChanged,
|
||||||
getNote,
|
update
|
||||||
getBranch,
|
|
||||||
getImage,
|
|
||||||
getNoteImage,
|
|
||||||
getNoteReordering,
|
|
||||||
getNoteRevision,
|
|
||||||
getRecentNote,
|
|
||||||
getOption,
|
|
||||||
getLabel,
|
|
||||||
getApiToken,
|
|
||||||
updateNote,
|
|
||||||
updateBranch,
|
|
||||||
updateImage,
|
|
||||||
updateNoteImage,
|
|
||||||
updateNoteReordering,
|
|
||||||
updateNoteRevision,
|
|
||||||
updateRecentNote,
|
|
||||||
updateOption,
|
|
||||||
updateLabel,
|
|
||||||
updateApiToken
|
|
||||||
};
|
};
|
||||||
@@ -147,26 +147,7 @@ function register(app) {
|
|||||||
apiRoute(POST, '/api/sync/force-full-sync', syncApiRoute.forceFullSync);
|
apiRoute(POST, '/api/sync/force-full-sync', syncApiRoute.forceFullSync);
|
||||||
apiRoute(POST, '/api/sync/force-note-sync/:noteId', syncApiRoute.forceNoteSync);
|
apiRoute(POST, '/api/sync/force-note-sync/:noteId', syncApiRoute.forceNoteSync);
|
||||||
apiRoute(GET, '/api/sync/changed', syncApiRoute.getChanged);
|
apiRoute(GET, '/api/sync/changed', syncApiRoute.getChanged);
|
||||||
apiRoute(GET, '/api/sync/notes/:noteId', syncApiRoute.getNote);
|
apiRoute(PUT, '/api/sync/update', syncApiRoute.update);
|
||||||
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/branches', syncApiRoute.updateBranch);
|
|
||||||
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(GET, '/api/event-log', eventLogRoute.getEventLog);
|
apiRoute(GET, '/api/event-log', eventLogRoute.getEventLog);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
const build = require('./build');
|
const build = require('./build');
|
||||||
const packageJson = require('../../package');
|
const packageJson = require('../../package');
|
||||||
|
|
||||||
const APP_DB_VERSION = 86;
|
const APP_DB_VERSION = 88;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
appVersion: packageJson.version,
|
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" };
|
||||||
|
|||||||
@@ -197,16 +197,6 @@ async function runAllChecks() {
|
|||||||
AND images.isDeleted = 1`,
|
AND images.isDeleted = 1`,
|
||||||
"Note image is not deleted while image is deleted for noteImageId", errorList);
|
"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(`
|
await runCheck(`
|
||||||
SELECT
|
SELECT
|
||||||
noteId
|
noteId
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const log = require('./log');
|
const log = require('./log');
|
||||||
|
const eventLogService = require('./event_log');
|
||||||
|
const messagingService = require('./messaging');
|
||||||
|
|
||||||
function getHash(rows) {
|
function getHash(rows) {
|
||||||
let hash = '';
|
let hash = '';
|
||||||
@@ -121,6 +125,29 @@ async function getHashes() {
|
|||||||
return hashes;
|
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 = {
|
module.exports = {
|
||||||
getHashes
|
getHashes,
|
||||||
|
checkContentHashes
|
||||||
};
|
};
|
||||||
@@ -11,7 +11,8 @@ const BUILTIN_LABELS = [
|
|||||||
'run',
|
'run',
|
||||||
'manualTransactionHandling',
|
'manualTransactionHandling',
|
||||||
'disableInclusion',
|
'disableInclusion',
|
||||||
'appCss'
|
'appCss',
|
||||||
|
'hideChildrenOverview'
|
||||||
];
|
];
|
||||||
|
|
||||||
async function getNotesWithLabel(name, value) {
|
async function getNotesWithLabel(name, value) {
|
||||||
|
|||||||
@@ -15,14 +15,22 @@ const logger = require('simple-node-logger').createRollingFileLogger({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function info(message) {
|
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) {
|
function error(message) {
|
||||||
|
message = "ERROR: " + message;
|
||||||
|
|
||||||
// we're using .info() instead of .error() because simple-node-logger emits weird error for showError()
|
// 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" ];
|
const requestBlacklist = [ "/libraries", "/javascripts", "/images", "/stylesheets" ];
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ async function createNewNote(parentNoteId, noteData) {
|
|||||||
noteId: note.noteId,
|
noteId: note.noteId,
|
||||||
parentNoteId: parentNoteId,
|
parentNoteId: parentNoteId,
|
||||||
notePosition: newNotePos,
|
notePosition: newNotePos,
|
||||||
|
prefix: noteData.prefix,
|
||||||
isExpanded: 0
|
isExpanded: 0
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
@@ -180,6 +181,8 @@ async function saveNoteRevision(note) {
|
|||||||
// title and text should be decrypted now
|
// title and text should be decrypted now
|
||||||
title: note.title,
|
title: note.title,
|
||||||
content: note.content,
|
content: note.content,
|
||||||
|
type: note.type,
|
||||||
|
mime: note.mime,
|
||||||
isProtected: 0, // will be fixed in the protectNoteRevisions() call
|
isProtected: 0, // will be fixed in the protectNoteRevisions() call
|
||||||
dateModifiedFrom: note.dateModified,
|
dateModifiedFrom: note.dateModified,
|
||||||
dateModifiedTo: dateUtils.nowDate()
|
dateModifiedTo: dateUtils.nowDate()
|
||||||
@@ -198,7 +201,7 @@ async function updateNote(noteId, noteUpdates) {
|
|||||||
await saveNoteRevision(note);
|
await saveNoteRevision(note);
|
||||||
|
|
||||||
note.title = noteUpdates.title;
|
note.title = noteUpdates.title;
|
||||||
note.content = noteUpdates.content;
|
note.setContent(noteUpdates.content);
|
||||||
note.isProtected = noteUpdates.isProtected;
|
note.isProtected = noteUpdates.isProtected;
|
||||||
await note.save();
|
await note.save();
|
||||||
|
|
||||||
|
|||||||
@@ -149,11 +149,13 @@ async function transactional(func) {
|
|||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
catch (e) {
|
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);
|
reject(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ const sourceIdService = require('./source_id');
|
|||||||
const dateUtils = require('./date_utils');
|
const dateUtils = require('./date_utils');
|
||||||
const syncUpdateService = require('./sync_update');
|
const syncUpdateService = require('./sync_update');
|
||||||
const contentHashService = require('./content_hash');
|
const contentHashService = require('./content_hash');
|
||||||
const eventLogService = require('./event_log');
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const appInfo = require('./app_info');
|
const appInfo = require('./app_info');
|
||||||
const messagingService = require('./messaging');
|
|
||||||
const syncSetup = require('./sync_setup');
|
const syncSetup = require('./sync_setup');
|
||||||
const syncMutexService = require('./sync_mutex');
|
const syncMutexService = require('./sync_mutex');
|
||||||
const cls = require('./cls');
|
const cls = require('./cls');
|
||||||
@@ -91,69 +89,19 @@ async function login() {
|
|||||||
return syncContext;
|
return syncContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLastSyncedPull() {
|
|
||||||
return parseInt(await optionService.getOption('lastSyncedPull'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setLastSyncedPull(syncId) {
|
|
||||||
await optionService.setOption('lastSyncedPull', syncId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pullSync(syncContext) {
|
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, entity} of rows) {
|
||||||
|
|
||||||
for (const sync of syncRows) {
|
|
||||||
if (sourceIdService.isLocalSourceId(sync.sourceId)) {
|
if (sourceIdService.isLocalSourceId(sync.sourceId)) {
|
||||||
log.info(`Skipping pull #${sync.id} ${sync.entityName} ${sync.entityId} because ${sync.sourceId} is a local source id.`);
|
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 {
|
else {
|
||||||
throw new Error(`Unrecognized entity type ${sync.entityName} in sync #${sync.id}`);
|
await syncUpdateService.updateEntity(sync, entity, syncContext.sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await setLastSyncedPull(sync.id);
|
await setLastSyncedPull(sync.id);
|
||||||
@@ -162,145 +110,69 @@ async function pullSync(syncContext) {
|
|||||||
log.info("Finished pull");
|
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) {
|
async function pushSync(syncContext) {
|
||||||
let lastSyncedPush = await getLastSyncedPush();
|
let lastSyncedPush = await getLastSyncedPush();
|
||||||
|
|
||||||
while (true) {
|
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) {
|
const filteredSyncs = syncs.filter(sync => {
|
||||||
// nothing to 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");
|
log.info("Nothing to push");
|
||||||
|
|
||||||
|
await setLastSyncedPush(lastSyncedPush);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sync.sourceId === syncContext.sourceId) {
|
const syncRecords = await getSyncRecords(filteredSyncs);
|
||||||
log.info(`Skipping push #${sync.id} ${sync.entityName} ${sync.entityId} because it originates from sync target`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
await pushEntity(sync, syncContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
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) {
|
async function checkContentHash(syncContext) {
|
||||||
const resp = await syncRequest(syncContext, 'GET', '/api/sync/check');
|
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.");
|
log.info("There are some outstanding pulls, skipping content check.");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastSyncedPush = await getLastSyncedPush();
|
const notPushedSyncs = await sql.getValue("SELECT COUNT(*) FROM sync WHERE id > ?", [await getLastSyncedPush()]);
|
||||||
const notPushedSyncs = await sql.getValue("SELECT COUNT(*) FROM sync WHERE id > ?", [lastSyncedPush]);
|
|
||||||
|
|
||||||
if (notPushedSyncs > 0) {
|
if (notPushedSyncs > 0) {
|
||||||
log.info("There's " + notPushedSyncs + " outstanding pushes, skipping content check.");
|
log.info(`There's ${notPushedSyncs} outstanding pushes, skipping content check.`);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashes = await contentHashService.getHashes();
|
await contentHashService.checkContentHashes(resp.hashes);
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncRequest(syncContext, method, uri, body) {
|
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(() => {
|
sqlInit.dbReady.then(() => {
|
||||||
if (syncSetup.isSyncSetup) {
|
if (syncSetup.isSyncSetup) {
|
||||||
log.info("Setting up sync to " + syncSetup.SYNC_SERVER + " with timeout " + syncSetup.SYNC_TIMEOUT);
|
log.info("Setting up sync to " + syncSetup.SYNC_SERVER + " with timeout " + syncSetup.SYNC_TIMEOUT);
|
||||||
@@ -357,5 +303,5 @@ sqlInit.dbReady.then(() => {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sync,
|
sync,
|
||||||
serializeNoteContentBuffer
|
getSyncRecords
|
||||||
};
|
};
|
||||||
@@ -91,6 +91,8 @@ async function fillSyncRows(entityName, entityKey) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fillAllSyncRows() {
|
async function fillAllSyncRows() {
|
||||||
|
await sql.execute("DELETE FROM sync");
|
||||||
|
|
||||||
await fillSyncRows("notes", "noteId");
|
await fillSyncRows("notes", "noteId");
|
||||||
await fillSyncRows("branches", "branchId");
|
await fillSyncRows("branches", "branchId");
|
||||||
await fillSyncRows("note_revisions", "noteRevisionId");
|
await fillSyncRows("note_revisions", "noteRevisionId");
|
||||||
|
|||||||
@@ -3,6 +3,44 @@ const log = require('./log');
|
|||||||
const eventLogService = require('./event_log');
|
const eventLogService = require('./event_log');
|
||||||
const syncTableService = require('./sync_table');
|
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) {
|
function deserializeNoteContentBuffer(note) {
|
||||||
if (note.type === 'file') {
|
if (note.type === 'file') {
|
||||||
note.content = new Buffer(note.content, 'binary');
|
note.content = new Buffer(note.content, 'binary');
|
||||||
@@ -58,13 +96,13 @@ async function updateNoteRevision(entity, sourceId) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateNoteReordering(entity, sourceId) {
|
async function updateNoteReordering(entityId, entity, sourceId) {
|
||||||
await sql.transactional(async () => {
|
await sql.transactional(async () => {
|
||||||
Object.keys(entity.ordering).forEach(async key => {
|
Object.keys(entity).forEach(async key => {
|
||||||
await sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity.ordering[key], key]);
|
await sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity[key], key]);
|
||||||
});
|
});
|
||||||
|
|
||||||
await syncTableService.addNoteReorderingSync(entity.parentNoteId, sourceId);
|
await syncTableService.addNoteReorderingSync(entityId, sourceId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,14 +197,5 @@ async function updateApiToken(entity, sourceId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
updateNote,
|
updateEntity
|
||||||
updateBranch,
|
|
||||||
updateNoteRevision,
|
|
||||||
updateNoteReordering,
|
|
||||||
updateOptions,
|
|
||||||
updateRecentNotes,
|
|
||||||
updateImage,
|
|
||||||
updateNoteImage,
|
|
||||||
updateLabel,
|
|
||||||
updateApiToken
|
|
||||||
};
|
};
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</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;">
|
<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"
|
<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>
|
style="background: url('/images/icons/file-plus.png')"></a>
|
||||||
@@ -54,28 +54,28 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="file" id="import-upload" style="display: none" />
|
<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 id="search-box" style="display: none; padding: 10px; margin-top: 10px;">
|
||||||
<div style="display: flex; align-items: center;">
|
<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">
|
<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>
|
<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>
|
||||||
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-evenly; margin-top: 10px;">
|
<div id="tree" class="hide-toggle" style="overflow: auto; flex-grow: 100; flex-shrink: 100; 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>
|
|
||||||
|
|
||||||
<div id="tree" class="hide-toggle" style="grid-area: tree; overflow: auto;">
|
<div id="parent-list">
|
||||||
</div>
|
<p><strong>Note locations:</strong></p>
|
||||||
|
|
||||||
<div id="parent-list" class="hide-toggle">
|
<ul id="parent-list-inner"></ul>
|
||||||
<p><strong>Note locations:</strong></p>
|
</div>
|
||||||
|
|
||||||
<ul id="parent-list-inner"></ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hide-toggle" style="grid-area: title;">
|
<div class="hide-toggle" style="grid-area: title;">
|
||||||
@@ -132,82 +132,86 @@
|
|||||||
</div>
|
</div>
|
||||||
</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-wrapper">
|
||||||
<div id="note-detail-text" class="note-detail-component"></div>
|
<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 id="note-detail-search" class="note-detail-component">
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center;">
|
||||||
<strong>Search string: </strong>
|
<strong>Search string: </strong>
|
||||||
<textarea rows="4" cols="50" id="search-string"></textarea>
|
<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>
|
</div>
|
||||||
|
|
||||||
<br />
|
<div id="note-detail-code" class="note-detail-component"></div>
|
||||||
|
|
||||||
<h4>Help</h4>
|
<div id="note-detail-render" class="note-detail-component"></div>
|
||||||
<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>
|
<div id="note-detail-file" class="note-detail-component">
|
||||||
</p>
|
<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>
|
||||||
|
|
||||||
<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">
|
<span id="label-list-inner"></span>
|
||||||
<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>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user