Compare commits
274 Commits
v0.10.1-be
...
v0.20.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d1bc46c04 | ||
|
|
4bc44605fb | ||
|
|
b868990fba | ||
|
|
26c06c9826 | ||
|
|
f5b89432a6 | ||
|
|
0e7372adbf | ||
|
|
d4fbe28517 | ||
|
|
668528d5eb | ||
|
|
17348a9cfe | ||
|
|
09b610701d | ||
|
|
71e687ad8e | ||
|
|
171877ce08 | ||
|
|
4f1e6ec70f | ||
|
|
1938c317c3 | ||
|
|
99d81059d0 | ||
|
|
59d5a86110 | ||
|
|
a5e56ea839 | ||
|
|
44f85224e7 | ||
|
|
0aa08b1c1e | ||
|
|
406d74c4d7 | ||
|
|
7f9a8a55ca | ||
|
|
a42bbba0e5 | ||
|
|
145efe67c3 | ||
|
|
513748836e | ||
|
|
427ce3972e | ||
|
|
02c0f9a6cd | ||
|
|
208771216e | ||
|
|
385d97a9b3 | ||
|
|
e39d1d08ac | ||
|
|
0f106fb96f | ||
|
|
df9acd0504 | ||
|
|
dbe0eb3f3a | ||
|
|
4513651e12 | ||
|
|
3204291463 | ||
|
|
510704a074 | ||
|
|
f440493e45 | ||
|
|
b897c6de13 | ||
|
|
acbd18e8fc | ||
|
|
ff5b84db10 | ||
|
|
16535f6a73 | ||
|
|
5b657ad961 | ||
|
|
bbbc3e9dc4 | ||
|
|
f43f0e10a1 | ||
|
|
6d842a65a2 | ||
|
|
50c4de021c | ||
|
|
936d8449f6 | ||
|
|
462bc0edd5 | ||
|
|
35ef3c8470 | ||
|
|
5117d43e29 | ||
|
|
7c9ac488e8 | ||
|
|
fec1574447 | ||
|
|
f7587de452 | ||
|
|
41a6e777ea | ||
|
|
8fb0de900b | ||
|
|
a40bf71fd4 | ||
|
|
2a53bb03ae | ||
|
|
a684879b91 | ||
|
|
ddbd4f73c8 | ||
|
|
b0ed790edf | ||
|
|
3424406ff1 | ||
|
|
ce5c385c15 | ||
|
|
cd9eef32b0 | ||
|
|
12d82e3b33 | ||
|
|
f071d3f651 | ||
|
|
297b536ebc | ||
|
|
7cca2d9247 | ||
|
|
36dc802d16 | ||
|
|
c78ddb70cb | ||
|
|
9fb0599c45 | ||
|
|
13f524fb39 | ||
|
|
27be3b4c90 | ||
|
|
af4ea66742 | ||
|
|
0f42c396f3 | ||
|
|
9e96272eb3 | ||
|
|
965dbcbc9a | ||
|
|
7ac109e7f7 | ||
|
|
ac25770c0e | ||
|
|
5b15424498 | ||
|
|
f1240c26bf | ||
|
|
1c0fd243d1 | ||
|
|
3491235533 | ||
|
|
5f36856571 | ||
|
|
d3e44b37e9 | ||
|
|
90e9297ec5 | ||
|
|
c568ef2f8a | ||
|
|
fcf6141cde | ||
|
|
21551d7b77 | ||
|
|
12031d369f | ||
|
|
b44c523845 | ||
|
|
49989695ff | ||
|
|
a55d3530e9 | ||
|
|
2aab3ad281 | ||
|
|
194ce4f10f | ||
|
|
2089c32839 | ||
|
|
f437be7af0 | ||
|
|
96dc56098d | ||
|
|
61987e46f7 | ||
|
|
509093b755 | ||
|
|
097114c0f2 | ||
|
|
040f9185f8 | ||
|
|
6dc934abbe | ||
|
|
2d24bf81dd | ||
|
|
9452fc236b | ||
|
|
365c37604b | ||
|
|
01c7e58d47 | ||
|
|
d3d49923b1 | ||
|
|
263ac299d0 | ||
|
|
3d185a5178 | ||
|
|
2ff7a890bc | ||
|
|
2eb1a9705f | ||
|
|
ed1381103a | ||
|
|
170d317589 | ||
|
|
ededc063df | ||
|
|
986eace1be | ||
|
|
29086d8dfe | ||
|
|
9b3f3fde05 | ||
|
|
6a50afd952 | ||
|
|
697eee2706 | ||
|
|
8a95afd756 | ||
|
|
4d6eda8fe6 | ||
|
|
e4f459fa2b | ||
|
|
f578e001b0 | ||
|
|
2a08aef885 | ||
|
|
7564bf388c | ||
|
|
7e4d70259f | ||
|
|
5b98c1c0f3 | ||
|
|
02dc7b199b | ||
|
|
d39cdbfada | ||
|
|
50bb4a47ee | ||
|
|
a4627f2ddb | ||
|
|
c8253caae9 | ||
|
|
0ece9bd1be | ||
|
|
b6935abcc9 | ||
|
|
37ab7b4641 | ||
|
|
013714cb5c | ||
|
|
1fe7c62f5a | ||
|
|
a06618d851 | ||
|
|
e7460ca3a9 | ||
|
|
073300bbcd | ||
|
|
a201661ce5 | ||
|
|
6235a3c886 | ||
|
|
3972c27e7a | ||
|
|
14cffbbe62 | ||
|
|
599c3c04af | ||
|
|
f1412b631d | ||
|
|
41908050bb | ||
|
|
f07033423c | ||
|
|
daf96fcbf2 | ||
|
|
2bca94529e | ||
|
|
b2c9a0da21 | ||
|
|
7a9542b4fc | ||
|
|
3a95c9e1bc | ||
|
|
3d2ef6be01 | ||
|
|
d67246699a | ||
|
|
14c704d6db | ||
|
|
4c8eeb2e6f | ||
|
|
c1b245c8b1 | ||
|
|
74202d67bb | ||
|
|
26066f39b1 | ||
|
|
b255cf190c | ||
|
|
bc77b143b0 | ||
|
|
9f0ff6ae7a | ||
|
|
736704c7d6 | ||
|
|
654c116c58 | ||
|
|
89a5cab98f | ||
|
|
c39d0be8cd | ||
|
|
e75b4cd848 | ||
|
|
378e8f35e5 | ||
|
|
bdb5e2f13f | ||
|
|
8211bed449 | ||
|
|
b243632483 | ||
|
|
e4d2513451 | ||
|
|
385144451b | ||
|
|
c8c533844e | ||
|
|
0e69f0c079 | ||
|
|
aee60c444f | ||
|
|
e7a504c66b | ||
|
|
45d9c7164c | ||
|
|
bd913a63a8 | ||
|
|
5a1938c078 | ||
|
|
015cd68756 | ||
|
|
76c0e5b2b8 | ||
|
|
0f8f707acd | ||
|
|
083cccea28 | ||
|
|
31b76b23ce | ||
|
|
af529f82e5 | ||
|
|
fc6669d254 | ||
|
|
c07785be67 | ||
|
|
80d2457b23 | ||
|
|
5dde2752d2 | ||
|
|
8bf4633cd0 | ||
|
|
bd66b8a1c8 | ||
|
|
be51e533fc | ||
|
|
f47ae12019 | ||
|
|
cab54a458f | ||
|
|
a30734f1bc | ||
|
|
7ad9f7b129 | ||
|
|
40a32e6826 | ||
|
|
ab0486aaf1 | ||
|
|
874593a167 | ||
|
|
03bf33630e | ||
|
|
933cce1b94 | ||
|
|
4a6ff573f8 | ||
|
|
1a737f7d19 | ||
|
|
cb69914f09 | ||
|
|
a372cbb2df | ||
|
|
0ce5caefe8 | ||
|
|
94dabb81f6 | ||
|
|
cd45bcfd03 | ||
|
|
49a53f7a45 | ||
|
|
9fa6c0918c | ||
|
|
e8d089e37e | ||
|
|
a931ce25fa | ||
|
|
b507abb4f7 | ||
|
|
66e7c6de62 | ||
|
|
4ce5ea9886 | ||
|
|
8c54b62f07 | ||
|
|
85eb50ed0f | ||
|
|
5ffd621e9d | ||
|
|
df93cb09da | ||
|
|
bbf04209f0 | ||
|
|
834bfa39c7 | ||
|
|
52b445f70b | ||
|
|
7b9b4fbb0c | ||
|
|
5af0ba1fcb | ||
|
|
85a9748291 | ||
|
|
b4005a7ffe | ||
|
|
82de1c88d4 | ||
|
|
1687ed7e0b | ||
|
|
c8b9c7d936 | ||
|
|
d57057ba28 | ||
|
|
66cee8daa4 | ||
|
|
afd7df0942 | ||
|
|
bd6ae33d32 | ||
|
|
70660a0d68 | ||
|
|
cdad18551a | ||
|
|
592c51d1a5 | ||
|
|
6a57b8a7e7 | ||
|
|
7a94e21c54 | ||
|
|
5b43f321e2 | ||
|
|
a4eafb934f | ||
|
|
7b59a665dd | ||
|
|
3d15450ffc | ||
|
|
b0c6d52461 | ||
|
|
2dc16dd29f | ||
|
|
d8924c536b | ||
|
|
3ebbf2cc46 | ||
|
|
f4079604c9 | ||
|
|
1f96a6beab | ||
|
|
b277a250e5 | ||
|
|
5b0e1a644d | ||
|
|
6bb3cfa9a3 | ||
|
|
9720868f5a | ||
|
|
8d8ee2a87a | ||
|
|
542e82ee5d | ||
|
|
0104b19502 | ||
|
|
120888b53e | ||
|
|
d2e2caed62 | ||
|
|
63066802a8 | ||
|
|
6128bb4ff3 | ||
|
|
982796255d | ||
|
|
36b15f474d | ||
|
|
13f71f8967 | ||
|
|
64336ffbee | ||
|
|
b09463d1b2 | ||
|
|
b5e6f46b9c | ||
|
|
08af4a0465 | ||
|
|
8c5df6321f | ||
|
|
d19f044961 | ||
|
|
e378d9f645 | ||
|
|
39dc0f71b4 | ||
|
|
0cef5c6b8c | ||
|
|
9b5a44cef4 | ||
|
|
29769ed91d |
4
.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
dist
|
||||||
|
.idea
|
||||||
3
.gitignore
vendored
@@ -6,4 +6,5 @@ yarn-error.log
|
|||||||
*.db
|
*.db
|
||||||
config.ini
|
config.ini
|
||||||
cert.key
|
cert.key
|
||||||
cert.crt
|
cert.crt
|
||||||
|
docs/
|
||||||
@@ -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.11">
|
||||||
<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>
|
||||||
@@ -10,10 +12,10 @@
|
|||||||
<collation id="4" parent="1" name="NOCASE"/>
|
<collation id="4" parent="1" name="NOCASE"/>
|
||||||
<collation id="5" parent="1" name="RTRIM"/>
|
<collation id="5" parent="1" name="RTRIM"/>
|
||||||
<table id="6" parent="2" name="api_tokens"/>
|
<table id="6" parent="2" name="api_tokens"/>
|
||||||
<table id="7" parent="2" name="branches"/>
|
<table id="7" parent="2" name="attributes"/>
|
||||||
<table id="8" parent="2" name="event_log"/>
|
<table id="8" parent="2" name="branches"/>
|
||||||
<table id="9" parent="2" name="images"/>
|
<table id="9" parent="2" name="event_log"/>
|
||||||
<table id="10" parent="2" name="labels"/>
|
<table id="10" parent="2" name="images"/>
|
||||||
<table id="11" parent="2" name="note_images"/>
|
<table id="11" parent="2" name="note_images"/>
|
||||||
<table id="12" parent="2" name="note_revisions"/>
|
<table id="12" parent="2" name="note_revisions"/>
|
||||||
<table id="13" parent="2" name="notes"/>
|
<table id="13" parent="2" name="notes"/>
|
||||||
@@ -48,539 +50,600 @@
|
|||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<DefaultExpression>0</DefaultExpression>
|
<DefaultExpression>0</DefaultExpression>
|
||||||
</column>
|
</column>
|
||||||
<index id="24" parent="6" name="sqlite_autoindex_api_tokens_1">
|
<column id="24" parent="6" name="hash">
|
||||||
|
<Position>5</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>""</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<index id="25" parent="6" name="sqlite_autoindex_api_tokens_1">
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>apiTokenId</ColNames>
|
<ColNames>apiTokenId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<key id="25" parent="6">
|
<key id="26" parent="6">
|
||||||
<ColNames>apiTokenId</ColNames>
|
<ColNames>apiTokenId</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_api_tokens_1</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_api_tokens_1</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<column id="26" parent="7" name="branchId">
|
<column id="27" parent="7" name="attributeId">
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="27" parent="7" name="noteId">
|
<column id="28" parent="7" 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="28" parent="7" name="parentNoteId">
|
<column id="29" parent="7" name="type">
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="29" parent="7" name="notePosition">
|
<column id="30" parent="7" name="name">
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
<DataType>INTEGER|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
</column>
|
</column>
|
||||||
<column id="30" parent="7" name="prefix">
|
<column id="31" parent="7" name="value">
|
||||||
<Position>5</Position>
|
<Position>5</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
</column>
|
|
||||||
<column id="31" parent="7" name="isExpanded">
|
|
||||||
<Position>6</Position>
|
|
||||||
<DataType>BOOLEAN|0s</DataType>
|
|
||||||
</column>
|
|
||||||
<column id="32" parent="7" name="isDeleted">
|
|
||||||
<Position>7</Position>
|
|
||||||
<DataType>INTEGER|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
<DefaultExpression>0</DefaultExpression>
|
|
||||||
</column>
|
|
||||||
<column id="33" parent="7" name="dateModified">
|
|
||||||
<Position>8</Position>
|
|
||||||
<DataType>TEXT|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
</column>
|
|
||||||
<index id="34" parent="7" name="sqlite_autoindex_branches_1">
|
|
||||||
<NameSurrogate>1</NameSurrogate>
|
|
||||||
<ColNames>branchId</ColNames>
|
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
<Unique>1</Unique>
|
|
||||||
</index>
|
|
||||||
<index id="35" parent="7" name="IDX_branches_noteId_parentNoteId">
|
|
||||||
<ColNames>noteId
|
|
||||||
parentNoteId</ColNames>
|
|
||||||
<ColumnCollations>
|
|
||||||
</ColumnCollations>
|
|
||||||
</index>
|
|
||||||
<index id="36" parent="7" name="IDX_branches_noteId">
|
|
||||||
<ColNames>noteId</ColNames>
|
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
</index>
|
|
||||||
<key id="37" parent="7">
|
|
||||||
<ColNames>branchId</ColNames>
|
|
||||||
<Primary>1</Primary>
|
|
||||||
<UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName>
|
|
||||||
</key>
|
|
||||||
<column id="38" parent="8" name="id">
|
|
||||||
<Position>1</Position>
|
|
||||||
<DataType>INTEGER|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
<SequenceIdentity>1</SequenceIdentity>
|
|
||||||
</column>
|
|
||||||
<column id="39" parent="8" name="noteId">
|
|
||||||
<Position>2</Position>
|
|
||||||
<DataType>TEXT|0s</DataType>
|
|
||||||
</column>
|
|
||||||
<column id="40" parent="8" name="comment">
|
|
||||||
<Position>3</Position>
|
|
||||||
<DataType>TEXT|0s</DataType>
|
|
||||||
</column>
|
|
||||||
<column id="41" parent="8" name="dateAdded">
|
|
||||||
<Position>4</Position>
|
|
||||||
<DataType>TEXT|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
</column>
|
|
||||||
<key id="42" parent="8">
|
|
||||||
<ColNames>id</ColNames>
|
|
||||||
<Primary>1</Primary>
|
|
||||||
</key>
|
|
||||||
<foreign-key id="43" parent="8">
|
|
||||||
<ColNames>noteId</ColNames>
|
|
||||||
<RefTableName>notes</RefTableName>
|
|
||||||
<RefColNames>noteId</RefColNames>
|
|
||||||
</foreign-key>
|
|
||||||
<column id="44" parent="9" name="imageId">
|
|
||||||
<Position>1</Position>
|
|
||||||
<DataType>TEXT|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
</column>
|
|
||||||
<column id="45" parent="9" name="format">
|
|
||||||
<Position>2</Position>
|
|
||||||
<DataType>TEXT|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
</column>
|
|
||||||
<column id="46" parent="9" name="checksum">
|
|
||||||
<Position>3</Position>
|
|
||||||
<DataType>TEXT|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
</column>
|
|
||||||
<column id="47" parent="9" name="name">
|
|
||||||
<Position>4</Position>
|
|
||||||
<DataType>TEXT|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
</column>
|
|
||||||
<column id="48" parent="9" name="data">
|
|
||||||
<Position>5</Position>
|
|
||||||
<DataType>BLOB|0s</DataType>
|
|
||||||
</column>
|
|
||||||
<column id="49" parent="9" name="isDeleted">
|
|
||||||
<Position>6</Position>
|
|
||||||
<DataType>INT|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
<DefaultExpression>0</DefaultExpression>
|
|
||||||
</column>
|
|
||||||
<column id="50" parent="9" name="dateModified">
|
|
||||||
<Position>7</Position>
|
|
||||||
<DataType>TEXT|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
</column>
|
|
||||||
<column id="51" parent="9" name="dateCreated">
|
|
||||||
<Position>8</Position>
|
|
||||||
<DataType>TEXT|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
</column>
|
|
||||||
<index id="52" parent="9" name="sqlite_autoindex_images_1">
|
|
||||||
<NameSurrogate>1</NameSurrogate>
|
|
||||||
<ColNames>imageId</ColNames>
|
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
<Unique>1</Unique>
|
|
||||||
</index>
|
|
||||||
<key id="53" parent="9">
|
|
||||||
<ColNames>imageId</ColNames>
|
|
||||||
<Primary>1</Primary>
|
|
||||||
<UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName>
|
|
||||||
</key>
|
|
||||||
<column id="54" parent="10" name="labelId">
|
|
||||||
<Position>1</Position>
|
|
||||||
<DataType>TEXT|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
</column>
|
|
||||||
<column id="55" parent="10" name="noteId">
|
|
||||||
<Position>2</Position>
|
|
||||||
<DataType>TEXT|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
</column>
|
|
||||||
<column id="56" parent="10" name="name">
|
|
||||||
<Position>3</Position>
|
|
||||||
<DataType>TEXT|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
</column>
|
|
||||||
<column id="57" parent="10" name="value">
|
|
||||||
<Position>4</Position>
|
|
||||||
<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="32" parent="7" name="position">
|
||||||
<Position>5</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="59" parent="10" name="dateCreated">
|
<column id="33" parent="7" name="dateCreated">
|
||||||
<Position>6</Position>
|
|
||||||
<DataType>TEXT|0s</DataType>
|
|
||||||
<NotNull>1</NotNull>
|
|
||||||
</column>
|
|
||||||
<column id="60" 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="34" parent="7" name="dateModified">
|
||||||
<Position>8</Position>
|
<Position>8</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<column id="35" parent="7" name="isDeleted">
|
||||||
|
<Position>9</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">
|
<column id="36" parent="7" name="hash">
|
||||||
|
<Position>10</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>""</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<column id="37" parent="7" name="isInheritable">
|
||||||
|
<Position>11</Position>
|
||||||
|
<DataType>int|0s</DataType>
|
||||||
|
<DefaultExpression>0</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<index id="38" parent="7" name="sqlite_autoindex_attributes_1">
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>labelId</ColNames>
|
<ColNames>attributeId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<index id="63" parent="10" name="IDX_labels_noteId">
|
<key id="39" parent="7">
|
||||||
<ColNames>noteId</ColNames>
|
<ColNames>attributeId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
</index>
|
|
||||||
<index id="64" parent="10" name="IDX_labels_name_value">
|
|
||||||
<ColNames>name
|
|
||||||
value</ColNames>
|
|
||||||
<ColumnCollations>
|
|
||||||
</ColumnCollations>
|
|
||||||
</index>
|
|
||||||
<key id="65" parent="10">
|
|
||||||
<ColNames>labelId</ColNames>
|
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName>
|
<UnderlyingIndexName>sqlite_autoindex_attributes_1</UnderlyingIndexName>
|
||||||
</key>
|
</key>
|
||||||
<column id="66" parent="11" name="noteImageId">
|
<column id="40" parent="8" 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="67" parent="11" name="noteId">
|
<column id="41" parent="8" 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="42" parent="8" name="parentNoteId">
|
||||||
<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="43" parent="8" name="notePosition">
|
||||||
|
<Position>4</Position>
|
||||||
|
<DataType>INTEGER|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<column id="44" parent="8" name="prefix">
|
||||||
|
<Position>5</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
</column>
|
||||||
|
<column id="45" parent="8" name="isExpanded">
|
||||||
|
<Position>6</Position>
|
||||||
|
<DataType>BOOLEAN|0s</DataType>
|
||||||
|
</column>
|
||||||
|
<column id="46" parent="8" name="isDeleted">
|
||||||
|
<Position>7</Position>
|
||||||
|
<DataType>INTEGER|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>0</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<column id="47" parent="8" name="dateModified">
|
||||||
|
<Position>8</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<column id="48" parent="8" name="hash">
|
||||||
|
<Position>9</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>""</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<column id="49" parent="8" name="dateCreated">
|
||||||
|
<Position>10</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>'1970-01-01T00:00:00.000Z'</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<index id="50" parent="8" name="sqlite_autoindex_branches_1">
|
||||||
|
<NameSurrogate>1</NameSurrogate>
|
||||||
|
<ColNames>branchId</ColNames>
|
||||||
|
<Unique>1</Unique>
|
||||||
|
</index>
|
||||||
|
<index id="51" parent="8" name="IDX_branches_noteId_parentNoteId">
|
||||||
|
<ColNames>noteId
|
||||||
|
parentNoteId</ColNames>
|
||||||
|
</index>
|
||||||
|
<index id="52" parent="8" name="IDX_branches_noteId">
|
||||||
|
<ColNames>noteId</ColNames>
|
||||||
|
</index>
|
||||||
|
<index id="53" parent="8" name="IDX_branches_parentNoteId">
|
||||||
|
<ColNames>parentNoteId</ColNames>
|
||||||
|
</index>
|
||||||
|
<key id="54" parent="8">
|
||||||
|
<ColNames>branchId</ColNames>
|
||||||
|
<Primary>1</Primary>
|
||||||
|
<UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName>
|
||||||
|
</key>
|
||||||
|
<column id="55" parent="9" name="eventId">
|
||||||
|
<Position>1</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<column id="56" parent="9" name="noteId">
|
||||||
|
<Position>2</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
</column>
|
||||||
|
<column id="57" parent="9" name="comment">
|
||||||
|
<Position>3</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
</column>
|
||||||
|
<column id="58" parent="9" name="dateCreated">
|
||||||
|
<Position>4</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<index id="59" parent="9" name="sqlite_autoindex_event_log_1">
|
||||||
|
<NameSurrogate>1</NameSurrogate>
|
||||||
|
<ColNames>eventId</ColNames>
|
||||||
|
<Unique>1</Unique>
|
||||||
|
</index>
|
||||||
|
<key id="60" parent="9">
|
||||||
|
<ColNames>eventId</ColNames>
|
||||||
|
<Primary>1</Primary>
|
||||||
|
<UnderlyingIndexName>sqlite_autoindex_event_log_1</UnderlyingIndexName>
|
||||||
|
</key>
|
||||||
|
<column id="61" parent="10" name="imageId">
|
||||||
|
<Position>1</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<column id="62" parent="10" name="format">
|
||||||
|
<Position>2</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<column id="63" parent="10" name="checksum">
|
||||||
|
<Position>3</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<column id="64" parent="10" name="name">
|
||||||
|
<Position>4</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<column id="65" parent="10" name="data">
|
||||||
|
<Position>5</Position>
|
||||||
|
<DataType>BLOB|0s</DataType>
|
||||||
|
</column>
|
||||||
|
<column id="66" parent="10" name="isDeleted">
|
||||||
|
<Position>6</Position>
|
||||||
|
<DataType>INT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>0</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<column id="67" parent="10" name="dateModified">
|
||||||
|
<Position>7</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<column id="68" parent="10" name="dateCreated">
|
||||||
|
<Position>8</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<column id="69" parent="10" name="hash">
|
||||||
|
<Position>9</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>""</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<index id="70" parent="10" name="sqlite_autoindex_images_1">
|
||||||
|
<NameSurrogate>1</NameSurrogate>
|
||||||
|
<ColNames>imageId</ColNames>
|
||||||
|
<Unique>1</Unique>
|
||||||
|
</index>
|
||||||
|
<key id="71" parent="10">
|
||||||
|
<ColNames>imageId</ColNames>
|
||||||
|
<Primary>1</Primary>
|
||||||
|
<UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName>
|
||||||
|
</key>
|
||||||
|
<column id="72" parent="11" name="noteImageId">
|
||||||
|
<Position>1</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<column id="73" parent="11" name="noteId">
|
||||||
|
<Position>2</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<column id="74" parent="11" name="imageId">
|
||||||
|
<Position>3</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<column id="75" 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="76" 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="77" 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">
|
<column id="78" parent="11" name="hash">
|
||||||
|
<Position>7</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>""</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<index id="79" parent="11" name="sqlite_autoindex_note_images_1">
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>noteImageId</ColNames>
|
<ColNames>noteImageId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<index id="73" parent="11" name="IDX_note_images_noteId_imageId">
|
<index id="80" parent="11" name="IDX_note_images_noteId_imageId">
|
||||||
<ColNames>noteId
|
<ColNames>noteId
|
||||||
imageId</ColNames>
|
imageId</ColNames>
|
||||||
<ColumnCollations>
|
|
||||||
</ColumnCollations>
|
|
||||||
</index>
|
</index>
|
||||||
<index id="74" parent="11" name="IDX_note_images_noteId">
|
<index id="81" parent="11" name="IDX_note_images_noteId">
|
||||||
<ColNames>noteId</ColNames>
|
<ColNames>noteId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
</index>
|
</index>
|
||||||
<index id="75" parent="11" name="IDX_note_images_imageId">
|
<index id="82" parent="11" name="IDX_note_images_imageId">
|
||||||
<ColNames>imageId</ColNames>
|
<ColNames>imageId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
</index>
|
</index>
|
||||||
<key id="76" parent="11">
|
<key id="83" 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="84" 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="85" 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="86" 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="87" 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="88" 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="89" 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="90" 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="91" parent="12" name="type">
|
||||||
|
<Position>8</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>''</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<column id="92" parent="12" name="mime">
|
||||||
|
<Position>9</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>''</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<column id="93" parent="12" name="hash">
|
||||||
|
<Position>10</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>""</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<index id="94" parent="12" name="sqlite_autoindex_note_revisions_1">
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>noteRevisionId</ColNames>
|
<ColNames>noteRevisionId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<index id="85" parent="12" name="IDX_note_revisions_noteId">
|
<index id="95" parent="12" name="IDX_note_revisions_noteId">
|
||||||
<ColNames>noteId</ColNames>
|
<ColNames>noteId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
</index>
|
</index>
|
||||||
<index id="86" parent="12" name="IDX_note_revisions_dateModifiedFrom">
|
<index id="96" parent="12" name="IDX_note_revisions_dateModifiedFrom">
|
||||||
<ColNames>dateModifiedFrom</ColNames>
|
<ColNames>dateModifiedFrom</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
</index>
|
</index>
|
||||||
<index id="87" parent="12" name="IDX_note_revisions_dateModifiedTo">
|
<index id="97" parent="12" name="IDX_note_revisions_dateModifiedTo">
|
||||||
<ColNames>dateModifiedTo</ColNames>
|
<ColNames>dateModifiedTo</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
</index>
|
</index>
|
||||||
<key id="88" parent="12">
|
<key id="98" 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="99" 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="100" parent="13" name="title">
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>"unnamed"</DefaultExpression>
|
||||||
</column>
|
</column>
|
||||||
<column id="91" parent="13" name="content">
|
<column id="101" parent="13" name="content">
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>""</DefaultExpression>
|
||||||
</column>
|
</column>
|
||||||
<column id="92" parent="13" name="isProtected">
|
<column id="102" 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="103" 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="104" 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="105" 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="106" 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="107" 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">
|
<column id="108" parent="13" name="hash">
|
||||||
|
<Position>10</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>""</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<index id="109" parent="13" name="sqlite_autoindex_notes_1">
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>noteId</ColNames>
|
<ColNames>noteId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<index id="99" parent="13" name="IDX_notes_isDeleted">
|
<index id="110" parent="13" name="IDX_notes_type">
|
||||||
<ColNames>isDeleted</ColNames>
|
<ColNames>type</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
</index>
|
</index>
|
||||||
<key id="100" parent="13">
|
<key id="111" 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="112" 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="113" 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="114" 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="115" 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">
|
<column id="116" parent="14" name="hash">
|
||||||
|
<Position>5</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>""</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<column id="117" parent="14" name="dateCreated">
|
||||||
|
<Position>6</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>'1970-01-01T00:00:00.000Z'</DefaultExpression>
|
||||||
|
</column>
|
||||||
|
<index id="118" parent="14" name="sqlite_autoindex_options_1">
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>name</ColNames>
|
<ColNames>name</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<key id="106" parent="14">
|
<key id="119" 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="120" 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="121" 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="122" parent="15" name="hash">
|
||||||
<Position>3</Position>
|
<Position>3</Position>
|
||||||
<DataType>TEXT|0s</DataType>
|
<DataType>TEXT|0s</DataType>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
|
<DefaultExpression>""</DefaultExpression>
|
||||||
</column>
|
</column>
|
||||||
<column id="110" parent="15" name="isDeleted">
|
<column id="123" parent="15" name="dateCreated">
|
||||||
<Position>4</Position>
|
<Position>4</Position>
|
||||||
|
<DataType>TEXT|0s</DataType>
|
||||||
|
<NotNull>1</NotNull>
|
||||||
|
</column>
|
||||||
|
<column id="124" parent="15" name="isDeleted">
|
||||||
|
<Position>5</Position>
|
||||||
<DataType>INT|0s</DataType>
|
<DataType>INT|0s</DataType>
|
||||||
</column>
|
</column>
|
||||||
<index id="111" parent="15" name="sqlite_autoindex_recent_notes_1">
|
<index id="125" parent="15" name="sqlite_autoindex_recent_notes_1">
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>branchId</ColNames>
|
<ColNames>branchId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<key id="112" parent="15">
|
<key id="126" 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="127" 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="128" 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="129" parent="16" name="sqlite_autoindex_source_ids_1">
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ColNames>sourceId</ColNames>
|
<ColNames>sourceId</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<key id="116" parent="16">
|
<key id="130" 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="131" 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="132" 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="133" 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="134" 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="135" 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="136" parent="18" name="name">
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="123" parent="18" name="seq">
|
<column id="137" parent="18" name="seq">
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
</column>
|
</column>
|
||||||
<column id="124" parent="19" name="id">
|
<column id="138" 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="139" 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="140" 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="141" 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="142" 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="143" parent="19" name="IDX_sync_entityName_entityId">
|
||||||
<ColNames>entityName
|
<ColNames>entityName
|
||||||
entityId</ColNames>
|
entityId</ColNames>
|
||||||
<ColumnCollations>
|
|
||||||
</ColumnCollations>
|
|
||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
</index>
|
</index>
|
||||||
<index id="130" parent="19" name="IDX_sync_syncDate">
|
<index id="144" parent="19" name="IDX_sync_syncDate">
|
||||||
<ColNames>syncDate</ColNames>
|
<ColNames>syncDate</ColNames>
|
||||||
<ColumnCollations></ColumnCollations>
|
|
||||||
</index>
|
</index>
|
||||||
<key id="131" parent="19">
|
<key id="145" parent="19">
|
||||||
<ColNames>id</ColNames>
|
<ColNames>id</ColNames>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
</key>
|
</key>
|
||||||
|
|||||||
6
.idea/sqldialects.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SqlDialectMappings">
|
||||||
|
<file url="PROJECT" dialect="SQLite" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
21
Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
FROM node:8.11.2
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y nasm
|
||||||
|
|
||||||
|
# Create app directory
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Install app dependencies
|
||||||
|
# A wildcard is used to ensure both package.json AND package-lock.json are copied
|
||||||
|
# where available (npm@5+)
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
RUN npm install --production
|
||||||
|
# If you are building your code for production
|
||||||
|
# RUN npm install --only=production
|
||||||
|
|
||||||
|
# Bundle app source
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD [ "node", "src/www" ]
|
||||||
8
bin/build-docker.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [[ $# -eq 0 ]] ; then
|
||||||
|
echo "Missing argument of new version"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo docker build -t zadam/trilium:latest -t zadam/trilium:$1 .
|
||||||
23
bin/build-pkg.sh
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [[ $# -eq 0 ]] ; then
|
||||||
|
echo "Missing argument of new version"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION=$1
|
||||||
|
|
||||||
|
PKG_DIR=dist/trilium-linux-x64-server
|
||||||
|
|
||||||
|
mkdir $PKG_DIR
|
||||||
|
|
||||||
|
pkg . --targets node8-linux-x64 --output ${PKG_DIR}/trilium
|
||||||
|
|
||||||
|
chmod +x ${PKG_DIR}/trilium
|
||||||
|
|
||||||
|
cp node_modules/sqlite3/lib/binding/node-v57-linux-x64/node_sqlite3.node ${PKG_DIR}/
|
||||||
|
cp node_modules/scrypt/build/Release/scrypt.node ${PKG_DIR}/
|
||||||
|
|
||||||
|
cd dist
|
||||||
|
|
||||||
|
7z a trilium-linux-x64-${VERSION}-server.7z trilium-linux-x64-server
|
||||||
9
bin/push-docker-image.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [[ $# -eq 0 ]] ; then
|
||||||
|
echo "Missing argument of new version"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo docker push zadam/trilium:latest
|
||||||
|
sudo docker push zadam/trilium:$1
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -47,6 +47,7 @@ bin/package.sh
|
|||||||
LINUX_X64_BUILD=trilium-linux-x64-$VERSION.7z
|
LINUX_X64_BUILD=trilium-linux-x64-$VERSION.7z
|
||||||
LINUX_IA32_BUILD=trilium-linux-ia32-$VERSION.7z
|
LINUX_IA32_BUILD=trilium-linux-ia32-$VERSION.7z
|
||||||
WINDOWS_X64_BUILD=trilium-windows-x64-$VERSION.7z
|
WINDOWS_X64_BUILD=trilium-windows-x64-$VERSION.7z
|
||||||
|
SERVER_BUILD=trilium-linux-x64-server-$VERSION.7z
|
||||||
|
|
||||||
echo "Creating release in GitHub"
|
echo "Creating release in GitHub"
|
||||||
|
|
||||||
@@ -75,4 +76,21 @@ github-release upload \
|
|||||||
--name "$WINDOWS_X64_BUILD" \
|
--name "$WINDOWS_X64_BUILD" \
|
||||||
--file "dist/$WINDOWS_X64_BUILD"
|
--file "dist/$WINDOWS_X64_BUILD"
|
||||||
|
|
||||||
|
echo "Packaging server version"
|
||||||
|
|
||||||
|
bin/build-pkg.sh $VERSION
|
||||||
|
|
||||||
|
github-release upload \
|
||||||
|
--tag $TAG \
|
||||||
|
--name "$SERVER_BUILD" \
|
||||||
|
--file "dist/$SERVER_BUILD"
|
||||||
|
|
||||||
|
echo "Building docker image"
|
||||||
|
|
||||||
|
bin/build-docker.sh $VERSION
|
||||||
|
|
||||||
|
echo "Pushing docker image to dockerhub"
|
||||||
|
|
||||||
|
bin/push-docker-image.sh $VERSION
|
||||||
|
|
||||||
echo "Release finished!"
|
echo "Release finished!"
|
||||||
@@ -3,15 +3,10 @@
|
|||||||
instanceName=
|
instanceName=
|
||||||
|
|
||||||
[Network]
|
[Network]
|
||||||
|
# port setting is relevant only for web deployments, desktop builds run on random free port
|
||||||
port=8080
|
port=8080
|
||||||
# true for TLS/SSL/HTTPS (secure), false for HTTP (unsecure).
|
# true for TLS/SSL/HTTPS (secure), false for HTTP (unsecure).
|
||||||
https=false
|
https=false
|
||||||
# path to certificate (run "bash generate-cert.sh" to generate self-signed certificate). Relevant only if https=true
|
# path to certificate (run "bash bin/generate-cert.sh" to generate self-signed certificate). Relevant only if https=true
|
||||||
certPath=
|
certPath=
|
||||||
keyPath=
|
keyPath=
|
||||||
|
|
||||||
[Sync]
|
|
||||||
syncServerHost=
|
|
||||||
syncServerTimeout=10000
|
|
||||||
syncProxy=
|
|
||||||
syncServerCertificate=
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('root', 'root', 'none', 0, null, 1, 0, '2018-01-01T00:00:00.000Z');
|
||||||
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('dLgtLUFn3GoN', '1Heh2acXfPNt', 'root', 21, null, 1, 0, '2017-12-23T00:46:39.304Z');
|
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('dLgtLUFn3GoN', '1Heh2acXfPNt', 'root', 21, null, 1, 0, '2017-12-23T00:46:39.304Z');
|
||||||
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('QLfS835GSfIh', '3RkyK9LI18dO', '1Heh2acXfPNt', 1, null, 1, 0, '2017-12-23T01:20:04.181Z');
|
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('QLfS835GSfIh', '3RkyK9LI18dO', '1Heh2acXfPNt', 1, null, 1, 0, '2017-12-23T01:20:04.181Z');
|
||||||
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('QJAcYJ1gGUh9', 'L1Ox40M1aEyy', '3RkyK9LI18dO', 0, null, 0, 0, '2017-12-23T01:20:45.365Z');
|
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('QJAcYJ1gGUh9', 'L1Ox40M1aEyy', '3RkyK9LI18dO', 0, null, 0, 0, '2017-12-23T01:20:45.365Z');
|
||||||
|
|||||||
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
@@ -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`
|
||||||
|
);
|
||||||
5
db/migrations/0089__add_root_branch.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, dateModified)
|
||||||
|
VALUES ('root', 'root', 'none', 0, null, 1, '2018-01-01T00:00:00.000Z');
|
||||||
|
|
||||||
|
INSERT INTO sync (entityName, entityId, sourceId, syncDate)
|
||||||
|
VALUES ('branches' ,'root', 'SYNC_FILL', '2018-01-01T00:00:00.000Z');
|
||||||
1
db/migrations/0090__branch_index.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId);
|
||||||
2
db/migrations/0091__drop_isDeleted_index.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- index confuses planner and is mostly useless anyway since we're mostly used in non-deleted notes (which are presumably majority)
|
||||||
|
DROP INDEX IDX_notes_isDeleted;
|
||||||
2
db/migrations/0092__add_type_index.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
create index IDX_notes_type
|
||||||
|
on notes (type);
|
||||||
9
db/migrations/0093__add_hash_field.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
ALTER TABLE notes ADD hash TEXT DEFAULT "" NOT NULL;
|
||||||
|
ALTER TABLE branches ADD hash TEXT DEFAULT "" NOT NULL;
|
||||||
|
ALTER TABLE note_revisions ADD hash TEXT DEFAULT "" NOT NULL;
|
||||||
|
ALTER TABLE recent_notes ADD hash TEXT DEFAULT "" NOT NULL;
|
||||||
|
ALTER TABLE options ADD hash TEXT DEFAULT "" NOT NULL;
|
||||||
|
ALTER TABLE note_images ADD hash TEXT DEFAULT "" NOT NULL;
|
||||||
|
ALTER TABLE images ADD hash TEXT DEFAULT "" NOT NULL;
|
||||||
|
ALTER TABLE labels ADD hash TEXT DEFAULT "" NOT NULL;
|
||||||
|
ALTER TABLE api_tokens ADD hash TEXT DEFAULT "" NOT NULL;
|
||||||
30
db/migrations/0094__unify_auditing_fields.sql
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
ALTER TABLE branches ADD dateCreated TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z';
|
||||||
|
|
||||||
|
CREATE TABLE `event_log_mig` (
|
||||||
|
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
`noteId` TEXT,
|
||||||
|
`comment` TEXT,
|
||||||
|
`dateCreated` TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO event_log_mig (id, noteId, comment, dateCreated)
|
||||||
|
SELECT id, noteId, comment, dateAdded FROM event_log;
|
||||||
|
|
||||||
|
DROP TABLE event_log;
|
||||||
|
ALTER TABLE event_log_mig RENAME TO event_log;
|
||||||
|
|
||||||
|
ALTER TABLE options ADD dateCreated TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z';
|
||||||
|
|
||||||
|
CREATE TABLE `recent_notes_mig` (
|
||||||
|
`branchId` TEXT NOT NULL PRIMARY KEY,
|
||||||
|
`notePath` TEXT NOT NULL,
|
||||||
|
hash TEXT DEFAULT "" NOT NULL,
|
||||||
|
`dateCreated` TEXT NOT NULL,
|
||||||
|
isDeleted INT
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO recent_notes_mig (branchId, notePath, hash, dateCreated, isDeleted)
|
||||||
|
SELECT branchId, notePath, hash, dateAccessed, isDeleted FROM recent_notes;
|
||||||
|
|
||||||
|
DROP TABLE recent_notes;
|
||||||
|
ALTER TABLE recent_notes_mig RENAME TO recent_notes;
|
||||||
1
db/migrations/0095__mime_type_for_render.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
UPDATE notes SET mime = 'text/html' WHERE type = 'render';
|
||||||
29
db/migrations/0096__unify_surrogate_keys.sql
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
CREATE TABLE `event_log_mig` (
|
||||||
|
`eventId` TEXT NOT NULL PRIMARY KEY,
|
||||||
|
`noteId` TEXT,
|
||||||
|
`comment` TEXT,
|
||||||
|
`dateCreated` TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO event_log_mig (eventId, noteId, comment, dateCreated)
|
||||||
|
SELECT id, noteId, comment, dateCreated FROM event_log;
|
||||||
|
|
||||||
|
DROP TABLE event_log;
|
||||||
|
ALTER TABLE event_log_mig RENAME TO event_log;
|
||||||
|
|
||||||
|
create table options_mig
|
||||||
|
(
|
||||||
|
optionId TEXT NOT NULL PRIMARY KEY,
|
||||||
|
name TEXT not null,
|
||||||
|
value TEXT,
|
||||||
|
dateModified INT,
|
||||||
|
isSynced INTEGER default 0 not null,
|
||||||
|
hash TEXT default "" not null,
|
||||||
|
dateCreated TEXT default '1970-01-01T00:00:00.000Z' not null
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO options_mig (optionId, name, value, dateModified, isSynced, hash, dateCreated)
|
||||||
|
SELECT name || "_key", name, value, dateModified, isSynced, hash, dateCreated FROM options;
|
||||||
|
|
||||||
|
DROP TABLE options;
|
||||||
|
ALTER TABLE options_mig RENAME TO options;
|
||||||
2
db/migrations/0097__add_zoomFactor.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
INSERT INTO options (optionId, name, value, dateCreated, dateModified, isSynced)
|
||||||
|
VALUES ('zoomFactor_key', 'zoomFactor', '1.0', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0);
|
||||||
1
db/migrations/0098__rename_hideInAutocomplete.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
UPDATE labels SET name = 'archived' WHERE name = 'hideInAutocomplete'
|
||||||
2
db/migrations/0099__add_theme_option.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
INSERT INTO options (optionId, name, value, dateCreated, dateModified, isSynced)
|
||||||
|
VALUES ('theme_key', 'theme', 'white', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0);
|
||||||
15
db/migrations/0100__remove_optionId.sql
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
create table options_mig
|
||||||
|
(
|
||||||
|
name TEXT not null PRIMARY KEY,
|
||||||
|
value TEXT,
|
||||||
|
dateModified INT,
|
||||||
|
isSynced INTEGER default 0 not null,
|
||||||
|
hash TEXT default "" not null,
|
||||||
|
dateCreated TEXT default '1970-01-01T00:00:00.000Z' not null
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO options_mig (name, value, dateModified, isSynced, hash, dateCreated)
|
||||||
|
SELECT name, value, dateModified, isSynced, hash, dateCreated FROM options;
|
||||||
|
|
||||||
|
DROP TABLE options;
|
||||||
|
ALTER TABLE options_mig RENAME TO options;
|
||||||
8
db/migrations/0101__add_sync_options.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
|
||||||
|
VALUES ('syncServerHost', '', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0);
|
||||||
|
|
||||||
|
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
|
||||||
|
VALUES ('syncServerTimeout', '5000', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0);
|
||||||
|
|
||||||
|
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
|
||||||
|
VALUES ('syncProxy', '', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0);
|
||||||
2
db/migrations/0102__fix_sync_entityIds.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DELETE FROM sync WHERE entityName = 'note_tree';
|
||||||
|
DELETE FROM sync WHERE entityName = 'attributes';
|
||||||
2
db/migrations/0103__add_initialized_option.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
|
||||||
|
VALUES ('initialized', 'true', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0);
|
||||||
4
db/migrations/0104__fill_sync_rows_for_options.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
const syncTableService = require('../../src/services/sync_table');
|
||||||
|
|
||||||
|
// options has not been filled so far which caused problems with clean-slate sync.
|
||||||
|
module.exports = async () => await syncTableService.fillAllSyncRows();
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
UPDATE notes SET content = '' WHERE isDeleted = 1;
|
||||||
|
UPDATE note_revisions SET content = '' WHERE (SELECT isDeleted FROM notes WHERE noteId = note_revisions.noteId) = 1;
|
||||||
15
db/migrations/0106__add_relations_table.sql
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
CREATE TABLE relations
|
||||||
|
(
|
||||||
|
relationId TEXT not null primary key,
|
||||||
|
sourceNoteId TEXT not null,
|
||||||
|
name TEXT not null,
|
||||||
|
targetNoteId TEXT not null,
|
||||||
|
position INT default 0 not null,
|
||||||
|
dateCreated TEXT not null,
|
||||||
|
dateModified TEXT not null,
|
||||||
|
isDeleted INT not null
|
||||||
|
, hash TEXT DEFAULT "" NOT NULL);
|
||||||
|
CREATE INDEX IDX_relation_sourceNoteId
|
||||||
|
on relations (sourceNoteId);
|
||||||
|
CREATE INDEX IDX_relation_targetNoteId
|
||||||
|
on relations (targetNoteId);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE relations ADD isInheritable int DEFAULT 0 NULL;
|
||||||
9
db/migrations/0108__new_backup_options.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
UPDATE options SET name = 'lastDailyBackupDate' WHERE name = 'lastBackupDate';
|
||||||
|
|
||||||
|
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
|
||||||
|
VALUES ('lastWeeklyBackupDate', '2018-07-29T18:31:00.874Z', '2018-07-29T18:31:00.874Z', '2018-07-29T18:31:00.874Z', 0);
|
||||||
|
|
||||||
|
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
|
||||||
|
VALUES ('lastMonthlyBackupDate', '2018-07-29T18:31:00.874Z', '2018-07-29T18:31:00.874Z', '2018-07-29T18:31:00.874Z', 0);
|
||||||
|
|
||||||
|
-- these options are not synced so no need to fix sync rows
|
||||||
27
db/migrations/0109__create_attributes.sql
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
create table attributes
|
||||||
|
(
|
||||||
|
attributeId TEXT not null primary key,
|
||||||
|
noteId TEXT not null,
|
||||||
|
type TEXT not null,
|
||||||
|
name TEXT not null,
|
||||||
|
value TEXT default '' not null,
|
||||||
|
position INT default 0 not null,
|
||||||
|
dateCreated TEXT not null,
|
||||||
|
dateModified TEXT not null,
|
||||||
|
isDeleted INT not null,
|
||||||
|
hash TEXT default "" not null);
|
||||||
|
|
||||||
|
create index IDX_attributes_name_value
|
||||||
|
on attributes (name, value);
|
||||||
|
|
||||||
|
create index IDX_attributes_value
|
||||||
|
on attributes (value);
|
||||||
|
|
||||||
|
create index IDX_attributes_noteId
|
||||||
|
on attributes (noteId);
|
||||||
|
|
||||||
|
INSERT INTO attributes (attributeId, noteId, type, name, value, position, dateCreated, dateModified, isDeleted, hash)
|
||||||
|
SELECT labelId, noteId, 'label', name, value, position, dateCreated, dateModified, isDeleted, hash FROM labels;
|
||||||
|
|
||||||
|
INSERT INTO attributes (attributeId, noteId, type, name, value, position, dateCreated, dateModified, isDeleted, hash)
|
||||||
|
SELECT relationId, sourceNoteId, 'relation', name, targetNoteId, position, dateCreated, dateModified, isDeleted, hash FROM relations;
|
||||||
1
db/migrations/0110__add_isInheritable_to_attributes.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE attributes ADD isInheritable int DEFAULT 0 NULL;
|
||||||
4
db/migrations/0111__cleanup_labels_and_relations.sql
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
DROP TABLE relations;
|
||||||
|
DROP TABLE labels;
|
||||||
|
|
||||||
|
DELETE FROM sync WHERE entityName = 'relations' OR entityName = 'labels';
|
||||||
1
db/migrations/0112__rename_inheritAttributes.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
UPDATE attributes SET name = 'template' WHERE name = 'inheritAttributes';
|
||||||
@@ -1,8 +1,3 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "options" (
|
|
||||||
`name` TEXT NOT NULL PRIMARY KEY,
|
|
||||||
`value` TEXT,
|
|
||||||
`dateModified` INT,
|
|
||||||
isSynced INTEGER NOT NULL DEFAULT 0);
|
|
||||||
CREATE TABLE IF NOT EXISTS "sync" (
|
CREATE TABLE IF NOT EXISTS "sync" (
|
||||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
`entityName` TEXT NOT NULL,
|
`entityName` TEXT NOT NULL,
|
||||||
@@ -21,28 +16,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 +24,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, hash TEXT DEFAULT "" NOT NULL);
|
||||||
CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` (
|
CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` (
|
||||||
`noteId`
|
`noteId`
|
||||||
);
|
);
|
||||||
@@ -71,7 +44,7 @@ CREATE TABLE IF NOT EXISTS "images"
|
|||||||
isDeleted INT NOT NULL DEFAULT 0,
|
isDeleted INT NOT NULL DEFAULT 0,
|
||||||
dateModified TEXT NOT NULL,
|
dateModified TEXT NOT NULL,
|
||||||
dateCreated TEXT NOT NULL
|
dateCreated TEXT NOT NULL
|
||||||
);
|
, hash TEXT DEFAULT "" NOT NULL);
|
||||||
CREATE TABLE note_images
|
CREATE TABLE note_images
|
||||||
(
|
(
|
||||||
noteImageId TEXT PRIMARY KEY NOT NULL,
|
noteImageId TEXT PRIMARY KEY NOT NULL,
|
||||||
@@ -80,7 +53,7 @@ CREATE TABLE note_images
|
|||||||
isDeleted INT NOT NULL DEFAULT 0,
|
isDeleted INT NOT NULL DEFAULT 0,
|
||||||
dateModified TEXT NOT NULL,
|
dateModified TEXT NOT NULL,
|
||||||
dateCreated TEXT NOT NULL
|
dateCreated TEXT NOT NULL
|
||||||
);
|
, hash TEXT DEFAULT "" NOT NULL);
|
||||||
CREATE INDEX IDX_note_images_noteId ON note_images (noteId);
|
CREATE INDEX IDX_note_images_noteId ON note_images (noteId);
|
||||||
CREATE INDEX IDX_note_images_imageId ON note_images (imageId);
|
CREATE INDEX IDX_note_images_imageId ON note_images (imageId);
|
||||||
CREATE INDEX IDX_note_images_noteId_imageId ON note_images (noteId, imageId);
|
CREATE INDEX IDX_note_images_noteId_imageId ON note_images (noteId, imageId);
|
||||||
@@ -90,7 +63,7 @@ CREATE TABLE IF NOT EXISTS "api_tokens"
|
|||||||
token TEXT NOT NULL,
|
token TEXT NOT NULL,
|
||||||
dateCreated TEXT NOT NULL,
|
dateCreated TEXT NOT NULL,
|
||||||
isDeleted INT NOT NULL DEFAULT 0
|
isDeleted INT NOT NULL DEFAULT 0
|
||||||
);
|
, hash TEXT DEFAULT "" NOT NULL);
|
||||||
CREATE TABLE IF NOT EXISTS "branches" (
|
CREATE TABLE IF NOT EXISTS "branches" (
|
||||||
`branchId` TEXT NOT NULL,
|
`branchId` TEXT NOT NULL,
|
||||||
`noteId` TEXT NOT NULL,
|
`noteId` TEXT NOT NULL,
|
||||||
@@ -99,7 +72,7 @@ CREATE TABLE IF NOT EXISTS "branches" (
|
|||||||
`prefix` TEXT,
|
`prefix` TEXT,
|
||||||
`isExpanded` BOOLEAN,
|
`isExpanded` BOOLEAN,
|
||||||
`isDeleted` INTEGER NOT NULL DEFAULT 0,
|
`isDeleted` INTEGER NOT NULL DEFAULT 0,
|
||||||
`dateModified` TEXT NOT NULL,
|
`dateModified` TEXT NOT NULL, hash TEXT DEFAULT "" NOT NULL, dateCreated TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z',
|
||||||
PRIMARY KEY(`branchId`)
|
PRIMARY KEY(`branchId`)
|
||||||
);
|
);
|
||||||
CREATE INDEX `IDX_branches_noteId` ON `branches` (
|
CREATE INDEX `IDX_branches_noteId` ON `branches` (
|
||||||
@@ -109,24 +82,52 @@ CREATE INDEX `IDX_branches_noteId_parentNoteId` ON `branches` (
|
|||||||
`noteId`,
|
`noteId`,
|
||||||
`parentNoteId`
|
`parentNoteId`
|
||||||
);
|
);
|
||||||
|
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', hash TEXT DEFAULT "" NOT NULL,
|
||||||
|
PRIMARY KEY(`noteId`)
|
||||||
|
);
|
||||||
|
CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId);
|
||||||
|
CREATE INDEX IDX_notes_type
|
||||||
|
on notes (type);
|
||||||
CREATE TABLE IF NOT EXISTS "recent_notes" (
|
CREATE TABLE IF NOT EXISTS "recent_notes" (
|
||||||
`branchId` TEXT NOT NULL PRIMARY KEY,
|
`branchId` TEXT NOT NULL PRIMARY KEY,
|
||||||
`notePath` TEXT NOT NULL,
|
`notePath` TEXT NOT NULL,
|
||||||
`dateAccessed` TEXT NOT NULL,
|
hash TEXT DEFAULT "" NOT NULL,
|
||||||
|
`dateCreated` TEXT NOT NULL,
|
||||||
isDeleted INT
|
isDeleted INT
|
||||||
);
|
);
|
||||||
CREATE TABLE labels
|
CREATE TABLE IF NOT EXISTS "event_log" (
|
||||||
|
`eventId` TEXT NOT NULL PRIMARY KEY,
|
||||||
|
`noteId` TEXT,
|
||||||
|
`comment` TEXT,
|
||||||
|
`dateCreated` TEXT NOT NULL
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS "options"
|
||||||
(
|
(
|
||||||
labelId TEXT not null primary key,
|
name TEXT not null PRIMARY KEY,
|
||||||
|
value TEXT,
|
||||||
|
dateModified INT,
|
||||||
|
isSynced INTEGER default 0 not null,
|
||||||
|
hash TEXT default "" not null,
|
||||||
|
dateCreated TEXT default '1970-01-01T00:00:00.000Z' not null
|
||||||
|
);
|
||||||
|
CREATE TABLE attributes
|
||||||
|
(
|
||||||
|
attributeId TEXT not null primary key,
|
||||||
noteId TEXT not null,
|
noteId TEXT not null,
|
||||||
|
type TEXT not null,
|
||||||
name TEXT not null,
|
name TEXT not null,
|
||||||
value TEXT default '' not null,
|
value TEXT default '' not null,
|
||||||
position INT default 0 not null,
|
position INT default 0 not null,
|
||||||
dateCreated TEXT not null,
|
dateCreated TEXT not null,
|
||||||
dateModified TEXT not null,
|
dateModified TEXT not null,
|
||||||
isDeleted INT not null
|
isDeleted INT not null,
|
||||||
);
|
hash TEXT default "" not null, isInheritable int DEFAULT 0 NULL);
|
||||||
CREATE INDEX IDX_labels_name_value
|
|
||||||
on labels (name, value);
|
|
||||||
CREATE INDEX IDX_labels_noteId
|
|
||||||
on labels (noteId);
|
|
||||||
|
|||||||
14
electron.js
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
const electron = require('electron');
|
const electron = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const config = require('./src/services/config');
|
|
||||||
const log = require('./src/services/log');
|
const log = require('./src/services/log');
|
||||||
const url = require("url");
|
const url = require("url");
|
||||||
|
const port = require('./src/services/port');
|
||||||
|
|
||||||
const app = electron.app;
|
const app = electron.app;
|
||||||
const globalShortcut = electron.globalShortcut;
|
const globalShortcut = electron.globalShortcut;
|
||||||
@@ -23,7 +23,7 @@ function onClosed() {
|
|||||||
mainWindow = null;
|
mainWindow = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMainWindow() {
|
async function createMainWindow() {
|
||||||
const win = new electron.BrowserWindow({
|
const win = new electron.BrowserWindow({
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 900,
|
height: 900,
|
||||||
@@ -31,14 +31,12 @@ function createMainWindow() {
|
|||||||
icon: path.join(__dirname, 'src/public/images/app-icons/png/256x256.png')
|
icon: path.join(__dirname, 'src/public/images/app-icons/png/256x256.png')
|
||||||
});
|
});
|
||||||
|
|
||||||
const port = config['Network']['port'] || '3000';
|
|
||||||
|
|
||||||
win.setMenu(null);
|
win.setMenu(null);
|
||||||
win.loadURL('http://localhost:' + port);
|
win.loadURL('http://localhost:' + await port);
|
||||||
win.on('closed', onClosed);
|
win.on('closed', onClosed);
|
||||||
|
|
||||||
win.webContents.on('new-window', (e, url) => {
|
win.webContents.on('new-window', (e, url) => {
|
||||||
if (url !== mainWindow.webContents.getURL()) {
|
if (url !== win.webContents.getURL()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
require('electron').shell.openExternal(url);
|
require('electron').shell.openExternal(url);
|
||||||
}
|
}
|
||||||
@@ -76,12 +74,12 @@ app.on('ready', () => {
|
|||||||
const dateNoteService = require('./src/services/date_notes');
|
const dateNoteService = require('./src/services/date_notes');
|
||||||
const dateUtils = require('./src/services/date_utils');
|
const dateUtils = require('./src/services/date_utils');
|
||||||
|
|
||||||
const parentNoteId = await dateNoteService.getDateNoteId(dateUtils.nowDate());
|
const parentNote = await dateNoteService.getDateNote(dateUtils.nowDate());
|
||||||
|
|
||||||
// window may be hidden / not in focus
|
// window may be hidden / not in focus
|
||||||
mainWindow.focus();
|
mainWindow.focus();
|
||||||
|
|
||||||
mainWindow.webContents.send('create-day-sub-note', parentNoteId);
|
mainWindow.webContents.send('create-day-sub-note', parentNote.noteId);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
|||||||
7093
package-lock.json
generated
120
package.json
@@ -1,74 +1,84 @@
|
|||||||
{
|
{
|
||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"description": "Trilium Notes",
|
"description": "Trilium Notes",
|
||||||
"version": "0.10.1-beta",
|
"version": "0.20.0",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
|
"bin": {
|
||||||
|
"trilium": "./src/www"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zadam/trilium.git"
|
"url": "https://github.com/zadam/trilium.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./bin/www",
|
"start": "node ./src/www",
|
||||||
"test-electron": "xo",
|
"test-electron": "xo",
|
||||||
"rebuild-electron": "electron-rebuild",
|
"rebuild-electron": "electron-rebuild",
|
||||||
"start-electron": "electron . --disable-gpu",
|
"start-electron": "electron . --disable-gpu",
|
||||||
"build-electron": "electron-packager . --out=dist --asar --overwrite --platform=win32,linux --arch=ia32,x64",
|
"build-electron": "electron-packager . --out=dist --asar --overwrite --platform=win32,linux --arch=ia32,x64 --app-version=",
|
||||||
"start-forge": "electron-forge start",
|
"start-forge": "electron-forge start",
|
||||||
"package-forge": "electron-forge package",
|
"package-forge": "electron-forge package",
|
||||||
"make-forge": "electron-forge make",
|
"make-forge": "electron-forge make",
|
||||||
"publish-forge": "electron-forge publish"
|
"publish-forge": "electron-forge publish",
|
||||||
|
"build-backend-docs": "jsdoc -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js",
|
||||||
|
"build-frontend-docs": "jsdoc -d ./docs/frontend_api src/public/javascripts/entities/*.js src/public/javascripts/services/frontend_script_api.js",
|
||||||
|
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async-mutex": "^0.1.3",
|
"async-mutex": "0.1.3",
|
||||||
"axios": "^0.17.1",
|
"axios": "0.18",
|
||||||
"body-parser": "~1.18.2",
|
"body-parser": "1.18.3",
|
||||||
"cls-hooked": "^4.2.2",
|
"cls-hooked": "4.2.2",
|
||||||
"cookie-parser": "~1.4.3",
|
"cookie-parser": "1.4.3",
|
||||||
"debug": "~3.1.0",
|
"debug": "3.1.0",
|
||||||
"devtron": "^1.4.0",
|
"devtron": "1.4.0",
|
||||||
"ejs": "~2.5.7",
|
"ejs": "2.6.1",
|
||||||
"electron": "^2.0.0-beta.5",
|
"electron-debug": "2.0.0",
|
||||||
"electron-debug": "^1.5.0",
|
"electron-dl": "1.12.0",
|
||||||
"electron-dl": "^1.11.0",
|
"electron-in-page-search": "1.3.2",
|
||||||
"electron-in-page-search": "^1.2.4",
|
"express": "4.16.3",
|
||||||
"electron-rebuild": "^1.7.3",
|
"express-session": "1.15.6",
|
||||||
"express": "~4.16.3",
|
"fs-extra": "7.0.0",
|
||||||
"express-session": "^1.15.6",
|
"get-port": "4.0.0",
|
||||||
"fs-extra": "^4.0.3",
|
"helmet": "3.13.0",
|
||||||
"helmet": "^3.12.0",
|
"html": "1.0.0",
|
||||||
"html": "^1.0.0",
|
"image-type": "3.0.0",
|
||||||
"image-type": "^3.0.0",
|
"imagemin": "6.0.0",
|
||||||
"imagemin": "^5.3.1",
|
"imagemin-giflossy": "5.1.10",
|
||||||
"imagemin-giflossy": "^5.1.10",
|
"imagemin-mozjpeg": "7.0.0",
|
||||||
"imagemin-mozjpeg": "^7.0.0",
|
"imagemin-pngquant": "6.0.0",
|
||||||
"imagemin-pngquant": "^5.1.0",
|
"ini": "1.3.5",
|
||||||
"ini": "^1.3.5",
|
"jimp": "0.3.5",
|
||||||
"jimp": "^0.2.28",
|
"moment": "2.22.2",
|
||||||
"moment": "^2.21.0",
|
"multer": "1.3.1",
|
||||||
"multer": "^1.3.0",
|
|
||||||
"open": "0.0.5",
|
"open": "0.0.5",
|
||||||
"rand-token": "^0.4.0",
|
"rand-token": "0.4.0",
|
||||||
"request": "^2.85.0",
|
"rcedit": "1.1.0",
|
||||||
"request-promise": "^4.2.2",
|
"request": "2.88.0",
|
||||||
"rimraf": "^2.6.2",
|
"request-promise": "4.2.2",
|
||||||
"sanitize-filename": "^1.6.1",
|
"rimraf": "2.6.2",
|
||||||
"scrypt": "^6.0.3",
|
"sanitize-filename": "1.6.1",
|
||||||
"serve-favicon": "~2.4.5",
|
"scrypt": "6.0.3",
|
||||||
"session-file-store": "^1.2.0",
|
"serve-favicon": "2.5.0",
|
||||||
"simple-node-logger": "^0.93.37",
|
"session-file-store": "1.2.0",
|
||||||
"sqlite": "^2.9.1",
|
"simple-node-logger": "0.93.37",
|
||||||
"tar-stream": "^1.5.5",
|
"sqlite": "3.0.0",
|
||||||
"unescape": "^1.0.1",
|
"tar-stream": "1.6.1",
|
||||||
"ws": "^3.3.3"
|
"unescape": "1.0.1",
|
||||||
|
"ws": "6.0.0",
|
||||||
|
"xml2js": "0.4.19"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron-compile": "^6.4.2",
|
"electron": "2.0.7",
|
||||||
"electron-packager": "^11.1.0",
|
"electron-compile": "6.4.3",
|
||||||
"electron-prebuilt-compile": "2.0.0-beta.5",
|
"electron-packager": "12.1.1",
|
||||||
"lorem-ipsum": "^1.0.4",
|
"electron-prebuilt-compile": "2.0.7",
|
||||||
"tape": "^4.9.0",
|
"electron-rebuild": "1.8.2",
|
||||||
"xo": "^0.18.0"
|
"lorem-ipsum": "1.0.5",
|
||||||
|
"tape": "4.9.1",
|
||||||
|
"xo": "0.22.0",
|
||||||
|
"pkg": "4.3.4"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"forge": {
|
"forge": {
|
||||||
@@ -107,5 +117,15 @@
|
|||||||
"node",
|
"node",
|
||||||
"browser"
|
"browser"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"pkg": {
|
||||||
|
"assets": [
|
||||||
|
"./db/**/*",
|
||||||
|
"./src/public/**/*",
|
||||||
|
"./src/views/**/*",
|
||||||
|
"./node_modules/mozjpeg/vendor/*",
|
||||||
|
"./node_modules/pngquant-bin/vendor/*",
|
||||||
|
"./node_modules/giflossy/vendor/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const os = require('os');
|
|||||||
const sessionSecret = require('./services/session_secret');
|
const sessionSecret = require('./services/session_secret');
|
||||||
const cls = require('./services/cls');
|
const cls = require('./services/cls');
|
||||||
require('./entities/entity_constructor');
|
require('./entities/entity_constructor');
|
||||||
|
require('./services/handlers');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ const sessionParser = session({
|
|||||||
cookie: {
|
cookie: {
|
||||||
// path: "/",
|
// path: "/",
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
maxAge: 1800000
|
maxAge: 24 * 60 * 60 * 1000 // in milliseconds
|
||||||
},
|
},
|
||||||
store: new FileStore({
|
store: new FileStore({
|
||||||
ttl: 30 * 24 * 3600,
|
ttl: 30 * 24 * 3600,
|
||||||
|
|||||||
@@ -3,13 +3,22 @@
|
|||||||
const Entity = require('./entity');
|
const Entity = require('./entity');
|
||||||
const dateUtils = require('../services/date_utils');
|
const dateUtils = require('../services/date_utils');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ApiToken is an entity representing token used to authenticate against Trilium API from client applications. Currently used only by Trilium Sender.
|
||||||
|
*
|
||||||
|
* @param {string} apiTokenId - primary key
|
||||||
|
* @param {string} token
|
||||||
|
* @param {boolean} isDeleted - true if API token is deleted
|
||||||
|
* @param {string} dateCreated
|
||||||
|
*
|
||||||
|
* @extends Entity
|
||||||
|
*/
|
||||||
class ApiToken extends Entity {
|
class ApiToken extends Entity {
|
||||||
static get tableName() { return "api_tokens"; }
|
static get entityName() { return "api_tokens"; }
|
||||||
static get primaryKeyName() { return "apiTokenId"; }
|
static get primaryKeyName() { return "apiTokenId"; }
|
||||||
|
static get hashedProperties() { return ["apiTokenId", "token", "dateCreated", "isDeleted"]; }
|
||||||
|
|
||||||
beforeSaving() {
|
beforeSaving() {
|
||||||
super.beforeSaving();
|
|
||||||
|
|
||||||
if (!this.isDeleted) {
|
if (!this.isDeleted) {
|
||||||
this.isDeleted = false;
|
this.isDeleted = false;
|
||||||
}
|
}
|
||||||
@@ -17,6 +26,8 @@ class ApiToken extends Entity {
|
|||||||
if (!this.dateCreated) {
|
if (!this.dateCreated) {
|
||||||
this.dateCreated = dateUtils.nowDate();
|
this.dateCreated = dateUtils.nowDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.beforeSaving();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
93
src/entities/attribute.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const Entity = require('./entity');
|
||||||
|
const repository = require('../services/repository');
|
||||||
|
const dateUtils = require('../services/date_utils');
|
||||||
|
const sql = require('../services/sql');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute is key value pair owned by a note.
|
||||||
|
*
|
||||||
|
* @param {string} attributeId
|
||||||
|
* @param {string} noteId
|
||||||
|
* @param {string} type
|
||||||
|
* @param {string} name
|
||||||
|
* @param {string} value
|
||||||
|
* @param {int} position
|
||||||
|
* @param {boolean} isInheritable
|
||||||
|
* @param {boolean} isDeleted
|
||||||
|
* @param {string} dateCreated
|
||||||
|
* @param {string} dateModified
|
||||||
|
*
|
||||||
|
* @extends Entity
|
||||||
|
*/
|
||||||
|
class Attribute extends Entity {
|
||||||
|
static get entityName() { return "attributes"; }
|
||||||
|
static get primaryKeyName() { return "attributeId"; }
|
||||||
|
static get hashedProperties() { return ["attributeId", "noteId", "type", "name", "value", "isInheritable", "isDeleted", "dateCreated"]; }
|
||||||
|
|
||||||
|
constructor(row) {
|
||||||
|
super(row);
|
||||||
|
|
||||||
|
this.isInheritable = !!this.isInheritable;
|
||||||
|
|
||||||
|
if (this.isDefinition()) {
|
||||||
|
try {
|
||||||
|
this.value = JSON.parse(this.value);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getNote() {
|
||||||
|
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTargetNote() {
|
||||||
|
if (this.type !== 'relation') {
|
||||||
|
throw new Error(`Attribute ${this.attributeId} is not relation`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
isDefinition() {
|
||||||
|
return this.type === 'label-definition' || this.type === 'relation-definition';
|
||||||
|
}
|
||||||
|
|
||||||
|
async beforeSaving() {
|
||||||
|
if (!this.value) {
|
||||||
|
// null value isn't allowed
|
||||||
|
this.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.position === undefined) {
|
||||||
|
this.position = 1 + await sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM attributes WHERE noteId = ?`, [this.noteId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isInheritable) {
|
||||||
|
this.isInheritable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isDeleted) {
|
||||||
|
this.isDeleted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.dateCreated) {
|
||||||
|
this.dateCreated = dateUtils.nowDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.beforeSaving();
|
||||||
|
|
||||||
|
if (this.isChanged) {
|
||||||
|
this.dateModified = dateUtils.nowDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Attribute;
|
||||||
@@ -5,17 +5,33 @@ const dateUtils = require('../services/date_utils');
|
|||||||
const repository = require('../services/repository');
|
const repository = require('../services/repository');
|
||||||
const sql = require('../services/sql');
|
const sql = require('../services/sql');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Branch represents note's placement in the tree - it's essentially pair of noteId and parentNoteId.
|
||||||
|
* Each note can have multiple (at least one) branches, meaning it can be placed into multiple places in the tree.
|
||||||
|
*
|
||||||
|
* @param {string} branchId - primary key
|
||||||
|
* @param {string} noteId
|
||||||
|
* @param {string} parentNoteId
|
||||||
|
* @param {int} notePosition
|
||||||
|
* @param {string} prefix
|
||||||
|
* @param {boolean} isExpanded
|
||||||
|
* @param {boolean} isDeleted
|
||||||
|
* @param {string} dateModified
|
||||||
|
* @param {string} dateCreated
|
||||||
|
*
|
||||||
|
* @extends Entity
|
||||||
|
*/
|
||||||
class Branch extends Entity {
|
class Branch extends Entity {
|
||||||
static get tableName() { return "branches"; }
|
static get entityName() { return "branches"; }
|
||||||
static get primaryKeyName() { return "branchId"; }
|
static get primaryKeyName() { return "branchId"; }
|
||||||
|
// notePosition is not part of hash because it would produce a lot of updates in case of reordering
|
||||||
|
static get hashedProperties() { return ["branchId", "noteId", "parentNoteId", "isDeleted", "prefix"]; }
|
||||||
|
|
||||||
async getNote() {
|
async getNote() {
|
||||||
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
|
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async beforeSaving() {
|
async beforeSaving() {
|
||||||
super.beforeSaving();
|
|
||||||
|
|
||||||
if (this.notePosition === undefined) {
|
if (this.notePosition === undefined) {
|
||||||
const maxNotePos = await sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [this.parentNoteId]);
|
const maxNotePos = await sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [this.parentNoteId]);
|
||||||
this.notePosition = maxNotePos === null ? 0 : maxNotePos + 1;
|
this.notePosition = maxNotePos === null ? 0 : maxNotePos + 1;
|
||||||
@@ -25,7 +41,15 @@ class Branch extends Entity {
|
|||||||
this.isDeleted = false;
|
this.isDeleted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dateModified = dateUtils.nowDate()
|
if (!this.dateCreated) {
|
||||||
|
this.dateCreated = dateUtils.nowDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.beforeSaving();
|
||||||
|
|
||||||
|
if (this.isChanged) {
|
||||||
|
this.dateModified = dateUtils.nowDate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,45 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const utils = require('../services/utils');
|
const utils = require('../services/utils');
|
||||||
const repository = require('../services/repository');
|
|
||||||
|
|
||||||
class Entity {
|
class Entity {
|
||||||
|
/**
|
||||||
|
* @param {object} [row] - database row representing given entity
|
||||||
|
*/
|
||||||
constructor(row = {}) {
|
constructor(row = {}) {
|
||||||
for (const key in row) {
|
for (const key in row) {
|
||||||
this[key] = row[key];
|
this[key] = row[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('isDeleted' in this) {
|
||||||
|
this.isDeleted = !!this.isDeleted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeSaving() {
|
beforeSaving() {
|
||||||
if (!this[this.constructor.primaryKeyName]) {
|
if (!this[this.constructor.primaryKeyName]) {
|
||||||
this[this.constructor.primaryKeyName] = utils.newEntityId();
|
this[this.constructor.primaryKeyName] = utils.newEntityId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const origHash = this.hash;
|
||||||
|
|
||||||
|
this.hash = this.generateHash();
|
||||||
|
|
||||||
|
this.isChanged = origHash !== this.hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateHash() {
|
||||||
|
let contentToHash = "";
|
||||||
|
|
||||||
|
for (const propertyName of this.constructor.hashedProperties) {
|
||||||
|
contentToHash += "|" + this[propertyName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.hash(contentToHash).substr(0, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
await repository.updateEntity(this);
|
await require('../services/repository').updateEntity(this);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,37 @@ const NoteRevision = require('../entities/note_revision');
|
|||||||
const Image = require('../entities/image');
|
const Image = require('../entities/image');
|
||||||
const NoteImage = require('../entities/note_image');
|
const NoteImage = require('../entities/note_image');
|
||||||
const Branch = require('../entities/branch');
|
const Branch = require('../entities/branch');
|
||||||
const Label = require('../entities/label');
|
const Attribute = require('../entities/attribute');
|
||||||
const RecentNote = require('../entities/recent_note');
|
const RecentNote = require('../entities/recent_note');
|
||||||
const ApiToken = require('../entities/api_token');
|
const ApiToken = require('../entities/api_token');
|
||||||
|
const Option = require('../entities/option');
|
||||||
const repository = require('../services/repository');
|
const repository = require('../services/repository');
|
||||||
|
|
||||||
|
const ENTITY_NAME_TO_ENTITY = {
|
||||||
|
"attributes": Attribute,
|
||||||
|
"images": Image,
|
||||||
|
"note_images": NoteImage,
|
||||||
|
"branches": Branch,
|
||||||
|
"notes": Note,
|
||||||
|
"note_revisions": NoteRevision,
|
||||||
|
"recent_notes": RecentNote,
|
||||||
|
"options": Option,
|
||||||
|
"api_tokens": ApiToken
|
||||||
|
};
|
||||||
|
|
||||||
|
function getEntityFromEntityName(entityName) {
|
||||||
|
if (!(entityName in ENTITY_NAME_TO_ENTITY)) {
|
||||||
|
throw new Error(`Entity for table ${entityName} not found!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ENTITY_NAME_TO_ENTITY[entityName];
|
||||||
|
}
|
||||||
|
|
||||||
function createEntityFromRow(row) {
|
function createEntityFromRow(row) {
|
||||||
let entity;
|
let entity;
|
||||||
|
|
||||||
if (row.labelId) {
|
if (row.attributeId) {
|
||||||
entity = new Label(row);
|
entity = new Attribute(row);
|
||||||
}
|
}
|
||||||
else if (row.noteRevisionId) {
|
else if (row.noteRevisionId) {
|
||||||
entity = new NoteRevision(row);
|
entity = new NoteRevision(row);
|
||||||
@@ -35,6 +56,9 @@ function createEntityFromRow(row) {
|
|||||||
else if (row.noteId) {
|
else if (row.noteId) {
|
||||||
entity = new Note(row);
|
entity = new Note(row);
|
||||||
}
|
}
|
||||||
|
else if (row.name) {
|
||||||
|
entity = new Option(row);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
throw new Error('Unknown entity type for row: ' + JSON.stringify(row));
|
throw new Error('Unknown entity type for row: ' + JSON.stringify(row));
|
||||||
}
|
}
|
||||||
@@ -42,8 +66,9 @@ function createEntityFromRow(row) {
|
|||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
repository.setEntityConstructor(createEntityFromRow);
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createEntityFromRow
|
createEntityFromRow,
|
||||||
};
|
getEntityFromEntityName
|
||||||
|
};
|
||||||
|
|
||||||
|
repository.setEntityConstructor(module.exports);
|
||||||
|
|||||||
@@ -3,13 +3,26 @@
|
|||||||
const Entity = require('./entity');
|
const Entity = require('./entity');
|
||||||
const dateUtils = require('../services/date_utils');
|
const dateUtils = require('../services/date_utils');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents image data.
|
||||||
|
*
|
||||||
|
* @param {string} imageId
|
||||||
|
* @param {string} format
|
||||||
|
* @param {string} checksum
|
||||||
|
* @param {string} name
|
||||||
|
* @param {blob} data
|
||||||
|
* @param {boolean} isDeleted
|
||||||
|
* @param {string} dateModified
|
||||||
|
* @param {string} dateCreated
|
||||||
|
*
|
||||||
|
* @extends Entity
|
||||||
|
*/
|
||||||
class Image extends Entity {
|
class Image extends Entity {
|
||||||
static get tableName() { return "images"; }
|
static get entityName() { return "images"; }
|
||||||
static get primaryKeyName() { return "imageId"; }
|
static get primaryKeyName() { return "imageId"; }
|
||||||
|
static get hashedProperties() { return ["imageId", "format", "checksum", "name", "isDeleted", "dateCreated"]; }
|
||||||
|
|
||||||
beforeSaving() {
|
beforeSaving() {
|
||||||
super.beforeSaving();
|
|
||||||
|
|
||||||
if (!this.isDeleted) {
|
if (!this.isDeleted) {
|
||||||
this.isDeleted = false;
|
this.isDeleted = false;
|
||||||
}
|
}
|
||||||
@@ -18,7 +31,11 @@ class Image extends Entity {
|
|||||||
this.dateCreated = dateUtils.nowDate();
|
this.dateCreated = dateUtils.nowDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dateModified = dateUtils.nowDate();
|
super.beforeSaving();
|
||||||
|
|
||||||
|
if (this.isChanged) {
|
||||||
|
this.dateModified = dateUtils.nowDate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const Entity = require('./entity');
|
|
||||||
const repository = require('../services/repository');
|
|
||||||
const dateUtils = require('../services/date_utils');
|
|
||||||
const sql = require('../services/sql');
|
|
||||||
|
|
||||||
class Label extends Entity {
|
|
||||||
static get tableName() { return "labels"; }
|
|
||||||
static get primaryKeyName() { return "labelId"; }
|
|
||||||
|
|
||||||
async getNote() {
|
|
||||||
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async beforeSaving() {
|
|
||||||
super.beforeSaving();
|
|
||||||
|
|
||||||
if (!this.value) {
|
|
||||||
// null value isn't allowed
|
|
||||||
this.value = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.position === undefined) {
|
|
||||||
this.position = 1 + await sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM labels WHERE noteId = ?`, [this.noteId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isDeleted) {
|
|
||||||
this.isDeleted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.dateCreated) {
|
|
||||||
this.dateCreated = dateUtils.nowDate();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dateModified = dateUtils.nowDate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Label;
|
|
||||||
@@ -1,39 +1,81 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Entity = require('./entity');
|
const Entity = require('./entity');
|
||||||
const protected_session = require('../services/protected_session');
|
const Attribute = require('./attribute');
|
||||||
|
const protectedSessionService = require('../services/protected_session');
|
||||||
const repository = require('../services/repository');
|
const repository = require('../services/repository');
|
||||||
const dateUtils = require('../services/date_utils');
|
const dateUtils = require('../services/date_utils');
|
||||||
|
|
||||||
class Note extends Entity {
|
const LABEL = 'label';
|
||||||
static get tableName() { return "notes"; }
|
const RELATION = 'relation';
|
||||||
static get primaryKeyName() { return "noteId"; }
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a Note which is a central object in the Trilium Notes project.
|
||||||
|
*
|
||||||
|
* @property {string} noteId - primary key
|
||||||
|
* @property {string} type - one of "text", "code", "file" or "render"
|
||||||
|
* @property {string} mime - MIME type, e.g. "text/html"
|
||||||
|
* @property {string} title - note title
|
||||||
|
* @property {string} content - note content - e.g. HTML text for text notes, file payload for files
|
||||||
|
* @property {boolean} isProtected - true if note is protected
|
||||||
|
* @property {boolean} isDeleted - true if note is deleted
|
||||||
|
* @property {string} dateCreated
|
||||||
|
* @property {string} dateModified
|
||||||
|
*
|
||||||
|
* @extends Entity
|
||||||
|
*/
|
||||||
|
class Note extends Entity {
|
||||||
|
static get entityName() { return "notes"; }
|
||||||
|
static get primaryKeyName() { return "noteId"; }
|
||||||
|
static get hashedProperties() { return ["noteId", "title", "content", "type", "isProtected", "isDeleted"]; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param row - object containing database row from "notes" table
|
||||||
|
*/
|
||||||
constructor(row) {
|
constructor(row) {
|
||||||
super(row);
|
super(row);
|
||||||
|
|
||||||
if (this.isProtected) {
|
this.isProtected = !!this.isProtected;
|
||||||
protected_session.decryptNote(this);
|
|
||||||
|
// check if there's noteId, otherwise this is a new entity which wasn't encrypted yet
|
||||||
|
if (this.isProtected && this.noteId) {
|
||||||
|
protectedSessionService.decryptNote(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isJson()) {
|
this.setContent(this.content);
|
||||||
this.jsonContent = JSON.parse(this.content);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setContent(content) {
|
||||||
|
this.content = content;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.jsonContent = JSON.parse(this.content);
|
||||||
|
}
|
||||||
|
catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {boolean} true if this note is the root of the note tree. Root note has "root" noteId */
|
||||||
|
isRoot() {
|
||||||
|
return this.noteId === 'root';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {boolean} true if this note is of application/json content type */
|
||||||
isJson() {
|
isJson() {
|
||||||
return this.mime === "application/json";
|
return this.mime === "application/json";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {boolean} true if this note is JavaScript (code or attachment) */
|
||||||
isJavaScript() {
|
isJavaScript() {
|
||||||
return (this.type === "code" || this.type === "file")
|
return (this.type === "code" || this.type === "file")
|
||||||
&& (this.mime.startsWith("application/javascript") || this.mime === "application/x-javascript");
|
&& (this.mime.startsWith("application/javascript") || this.mime === "application/x-javascript");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {boolean} true if this note is HTML */
|
||||||
isHtml() {
|
isHtml() {
|
||||||
return (this.type === "code" || this.type === "file") && this.mime === "text/html";
|
return (this.type === "code" || this.type === "file" || this.type === "render") && this.mime === "text/html";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {string} JS script environment - either "frontend" or "backend" */
|
||||||
getScriptEnv() {
|
getScriptEnv() {
|
||||||
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith('env=frontend'))) {
|
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith('env=frontend'))) {
|
||||||
return "frontend";
|
return "frontend";
|
||||||
@@ -50,55 +92,385 @@ class Note extends Entity {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLabels() {
|
/**
|
||||||
return await repository.getEntities("SELECT * FROM labels WHERE noteId = ? AND isDeleted = 0", [this.noteId]);
|
* @returns {Promise<Attribute[]>} attributes belonging to this specific note (excludes inherited attributes)
|
||||||
|
*/
|
||||||
|
async getOwnedAttributes() {
|
||||||
|
return await repository.getEntities(`SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ?`, [this.noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARNING: this doesn't take into account the possibility to have multi-valued labels!
|
/** @returns {Promise<Attribute[]>} all note's attributes, including inherited ones */
|
||||||
async getLabelMap() {
|
async getAttributes() {
|
||||||
const map = {};
|
if (!this.__attributeCache) {
|
||||||
|
await this.loadAttributesToCache();
|
||||||
for (const label of await this.getLabels()) {
|
|
||||||
map[label.name] = label.value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return map;
|
return this.__attributeCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasLabel(name) {
|
/** @returns {Promise<Attribute[]>} all note's labels (attributes with type label), including inherited ones */
|
||||||
const map = await this.getLabelMap();
|
async getLabels() {
|
||||||
|
return (await this.getAttributes()).filter(attr => attr.type === LABEL);
|
||||||
return map.hasOwnProperty(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARNING: this doesn't take into account the possibility to have multi-valued labels!
|
/** @returns {Promise<Attribute[]>} all note's relations (attributes with type relation), including inherited ones */
|
||||||
async getLabel(name) {
|
async getRelations() {
|
||||||
return await repository.getEntity("SELECT * FROM labels WHERE noteId = ? AND name = ?", [this.noteId, name]);
|
return (await this.getAttributes()).filter(attr => attr.type === RELATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear note's attributes cache to force fresh reload for next attribute request.
|
||||||
|
* Cache is note instance scoped.
|
||||||
|
*/
|
||||||
|
invalidateAttributeCache() {
|
||||||
|
this.__attributeCache = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {Promise<void>} */
|
||||||
|
async loadAttributesToCache() {
|
||||||
|
const attributes = await repository.getEntities(`
|
||||||
|
WITH RECURSIVE
|
||||||
|
tree(noteId, level) AS (
|
||||||
|
SELECT ?, 0
|
||||||
|
UNION
|
||||||
|
SELECT branches.parentNoteId, tree.level + 1 FROM branches
|
||||||
|
JOIN tree ON branches.noteId = tree.noteId
|
||||||
|
JOIN notes ON notes.noteId = branches.parentNoteId
|
||||||
|
WHERE notes.isDeleted = 0
|
||||||
|
AND branches.isDeleted = 0
|
||||||
|
),
|
||||||
|
treeWithAttrs(noteId, level) AS (
|
||||||
|
SELECT * FROM tree
|
||||||
|
UNION
|
||||||
|
SELECT attributes.value, treeWithAttrs.level + 1 FROM attributes
|
||||||
|
JOIN treeWithAttrs ON treeWithAttrs.noteId = attributes.noteId
|
||||||
|
WHERE attributes.isDeleted = 0
|
||||||
|
AND attributes.type = 'relation'
|
||||||
|
AND attributes.name = 'template'
|
||||||
|
AND (attributes.noteId = ? OR attributes.isInheritable = 1)
|
||||||
|
)
|
||||||
|
SELECT attributes.* FROM attributes JOIN treeWithAttrs ON attributes.noteId = treeWithAttrs.noteId
|
||||||
|
WHERE attributes.isDeleted = 0 AND (attributes.isInheritable = 1 OR attributes.noteId = ?)
|
||||||
|
ORDER BY level, noteId, position`, [this.noteId, this.noteId, this.noteId]);
|
||||||
|
// attributes are ordered so that "closest" attributes are first
|
||||||
|
// we order by noteId so that attributes from same note stay together. Actual noteId ordering doesn't matter.
|
||||||
|
|
||||||
|
const filteredAttributes = attributes.filter((attr, index) => {
|
||||||
|
if (attr.isDefinition()) {
|
||||||
|
const firstDefinitionIndex = attributes.findIndex(el => el.type === attr.type && el.name === attr.name);
|
||||||
|
|
||||||
|
// keep only if this element is the first definition for this type & name
|
||||||
|
return firstDefinitionIndex === index;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const definitionAttr = attributes.find(el => el.type === attr.type + '-definition' && el.name === attr.name);
|
||||||
|
|
||||||
|
if (!definitionAttr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const definition = definitionAttr.value;
|
||||||
|
|
||||||
|
if (definition.multiplicityType === 'multivalue') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const firstAttrIndex = attributes.findIndex(el => el.type === attr.type && el.name === attr.name);
|
||||||
|
|
||||||
|
// in case of single-valued attribute we'll keep it only if it's first (closest)
|
||||||
|
return firstAttrIndex === index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const attr of filteredAttributes) {
|
||||||
|
attr.isOwned = attr.noteId === this.noteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__attributeCache = filteredAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} type - attribute type (label, relation, etc.)
|
||||||
|
* @param {string} name - attribute name
|
||||||
|
* @returns {Promise<boolean>} true if note has an attribute with given type and name (including inherited)
|
||||||
|
*/
|
||||||
|
async hasAttribute(type, name) {
|
||||||
|
return !!await this.getAttribute(type, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} type - attribute type (label, relation, etc.)
|
||||||
|
* @param {string} name - attribute name
|
||||||
|
* @returns {Promise<Attribute>} attribute of given type and name. If there's more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
|
||||||
|
*/
|
||||||
|
async getAttribute(type, name) {
|
||||||
|
const attributes = await this.getAttributes();
|
||||||
|
|
||||||
|
return attributes.find(attr => attr.type === type && attr.name === name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} type - attribute type (label, relation, etc.)
|
||||||
|
* @param {string} name - attribute name
|
||||||
|
* @returns {Promise<string>} attribute value of given type and name or null if no such attribute exists.
|
||||||
|
*/
|
||||||
|
async getAttributeValue(type, name) {
|
||||||
|
const attr = await this.getAttribute(type, name);
|
||||||
|
|
||||||
|
return attr ? attr.value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on enabled, attribute is either set or removed.
|
||||||
|
*
|
||||||
|
* @param {string} type - attribute type ('relation', 'label' etc.)
|
||||||
|
* @param {boolean} enabled - toggle On or Off
|
||||||
|
* @param {string} name - attribute name
|
||||||
|
* @param {string} [value] - attribute value (optional)
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async toggleAttribute(type, enabled, name, value) {
|
||||||
|
if (enabled) {
|
||||||
|
await this.setAttribute(type, name, value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await this.removeAttribute(type, name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates given attribute name-value pair if it doesn't exist.
|
||||||
|
*
|
||||||
|
* @param {string} type - attribute type (label, relation, etc.)
|
||||||
|
* @param {string} name - attribute name
|
||||||
|
* @param {string} [value] - attribute value (optional)
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async setAttribute(type, name, value) {
|
||||||
|
const attributes = await this.getOwnedAttributes();
|
||||||
|
let attr = attributes.find(attr => attr.type === type && (value === undefined || attr.value === value));
|
||||||
|
|
||||||
|
if (!attr) {
|
||||||
|
attr = new Attribute({
|
||||||
|
noteId: this.noteId,
|
||||||
|
type: type,
|
||||||
|
name: name,
|
||||||
|
value: value !== undefined ? value : ""
|
||||||
|
});
|
||||||
|
|
||||||
|
await attr.save();
|
||||||
|
|
||||||
|
this.invalidateAttributeCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes given attribute name-value pair if it exists.
|
||||||
|
*
|
||||||
|
* @param {string} type - attribute type (label, relation, etc.)
|
||||||
|
* @param {string} name - attribute name
|
||||||
|
* @param {string} [value] - attribute value (optional)
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async removeAttribute(type, name, value) {
|
||||||
|
const attributes = await this.getOwnedAttributes();
|
||||||
|
|
||||||
|
for (const attribute of attributes) {
|
||||||
|
if (attribute.type === type && (value === undefined || value === attribute.value)) {
|
||||||
|
attribute.isDeleted = true;
|
||||||
|
await attribute.save();
|
||||||
|
|
||||||
|
this.invalidateAttributeCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name - label name
|
||||||
|
* @returns {Promise<boolean>} true if label exists (including inherited)
|
||||||
|
*/
|
||||||
|
async hasLabel(name) { return await this.hasAttribute(LABEL, name); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name - relation name
|
||||||
|
* @returns {Promise<boolean>} true if relation exists (including inherited)
|
||||||
|
*/
|
||||||
|
async hasRelation(name) { return await this.hasAttribute(RELATION, name); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name - label name
|
||||||
|
* @returns {Promise<Attribute>} label if it exists, null otherwise
|
||||||
|
*/
|
||||||
|
async getLabel(name) { return await this.getAttribute(LABEL, name); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name - relation name
|
||||||
|
* @returns {Promise<Attribute>} relation if it exists, null otherwise
|
||||||
|
*/
|
||||||
|
async getRelation(name) { return await this.getAttribute(RELATION, name); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name - label name
|
||||||
|
* @returns {Promise<string>} label value if label exists, null otherwise
|
||||||
|
*/
|
||||||
|
async getLabelValue(name) { return await this.getAttributeValue(LABEL, name); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name - relation name
|
||||||
|
* @returns {Promise<string>} relation value if relation exists, null otherwise
|
||||||
|
*/
|
||||||
|
async getRelationValue(name) { return await this.getAttributeValue(RELATION, name); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on enabled, label is either set or removed.
|
||||||
|
*
|
||||||
|
* @param {boolean} enabled - toggle On or Off
|
||||||
|
* @param {string} name - label name
|
||||||
|
* @param {string} [value] - label value (optional)
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async toggleLabel(enabled, name, value) { return await this.toggleAttribute(LABEL, enabled, name, value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on enabled, relation is either set or removed.
|
||||||
|
*
|
||||||
|
* @param {boolean} enabled - toggle On or Off
|
||||||
|
* @param {string} name - relation name
|
||||||
|
* @param {string} [value] - relation value (noteId)
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async toggleRelation(enabled, name, value) { return await this.toggleAttribute(RELATION, enabled, name, value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create label name-value pair if it doesn't exist yet.
|
||||||
|
*
|
||||||
|
* @param {string} name - label name
|
||||||
|
* @param {string} [value] - label value
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async setLabel(name, value) { return await this.setAttribute(LABEL, name, value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create relation name-value pair if it doesn't exist yet.
|
||||||
|
*
|
||||||
|
* @param {string} name - relation name
|
||||||
|
* @param {string} [value] - relation value (noteId)
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async setRelation(name, value) { return await this.setAttribute(RELATION, name, value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove label name-value pair, if it exists.
|
||||||
|
*
|
||||||
|
* @param {string} name - label name
|
||||||
|
* @param {string} [value] - label value
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async removeLabel(name, value) { return await this.removeAttribute(LABEL, name, value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove relation name-value pair, if it exists.
|
||||||
|
*
|
||||||
|
* @param {string} name - relation name
|
||||||
|
* @param {string} [value] - relation value (noteId)
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async removeRelation(name, value) { return await this.removeAttribute(RELATION, name, value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @returns {Promise<Note>|null} target note of the relation or null (if target is empty or note was not found)
|
||||||
|
*/
|
||||||
|
async getRelationTarget(name) {
|
||||||
|
const relation = await this.getRelation(name);
|
||||||
|
|
||||||
|
return relation ? await repository.getNote(relation.value) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds notes with given attribute name and value. Only own attributes are considered, not inherited ones
|
||||||
|
*
|
||||||
|
* @param {string} type - attribute type (label, relation, etc.)
|
||||||
|
* @param {string} name - attribute name
|
||||||
|
* @param {string} [value] - attribute value
|
||||||
|
* @returns {Promise<Note[]>}
|
||||||
|
*/
|
||||||
|
async findNotesWithAttribute(type, name, value) {
|
||||||
|
const params = [this.noteId, name];
|
||||||
|
let valueCondition = "";
|
||||||
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
params.push(value);
|
||||||
|
valueCondition = " AND attributes.value = ?";
|
||||||
|
}
|
||||||
|
|
||||||
|
const notes = await repository.getEntities(`
|
||||||
|
WITH RECURSIVE
|
||||||
|
tree(noteId) AS (
|
||||||
|
SELECT ?
|
||||||
|
UNION
|
||||||
|
SELECT branches.noteId FROM branches
|
||||||
|
JOIN tree ON branches.parentNoteId = tree.noteId
|
||||||
|
JOIN notes ON notes.noteId = branches.noteId
|
||||||
|
WHERE notes.isDeleted = 0
|
||||||
|
AND branches.isDeleted = 0
|
||||||
|
)
|
||||||
|
SELECT notes.* FROM notes
|
||||||
|
JOIN tree ON tree.noteId = notes.noteId
|
||||||
|
JOIN attributes ON attributes.noteId = notes.noteId
|
||||||
|
WHERE attributes.isDeleted = 0
|
||||||
|
AND attributes.name = ?
|
||||||
|
${valueCondition}
|
||||||
|
ORDER BY noteId, position`, params);
|
||||||
|
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds notes with given label name and value. Only own labels are considered, not inherited ones
|
||||||
|
*
|
||||||
|
* @param {string} name - label name
|
||||||
|
* @param {string} [value] - label value
|
||||||
|
* @returns {Promise<Note[]>}
|
||||||
|
*/
|
||||||
|
async findNotesWithLabel(name, value) { return await this.findNotesWithAttribute(LABEL, name, value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds notes with given relation name and value. Only own relations are considered, not inherited ones
|
||||||
|
*
|
||||||
|
* @param {string} name - relation name
|
||||||
|
* @param {string} [value] - relation value
|
||||||
|
* @returns {Promise<Note[]>}
|
||||||
|
*/
|
||||||
|
async findNotesWithRelation(name, value) { return await this.findNotesWithAttribute(RELATION, name, value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns note revisions of this note.
|
||||||
|
*
|
||||||
|
* @returns {Promise<NoteRevision[]>}
|
||||||
|
*/
|
||||||
async getRevisions() {
|
async getRevisions() {
|
||||||
return await repository.getEntities("SELECT * FROM note_revisions WHERE noteId = ?", [this.noteId]);
|
return await repository.getEntities("SELECT * FROM note_revisions WHERE noteId = ?", [this.noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<NoteImage[]>}
|
||||||
|
*/
|
||||||
async getNoteImages() {
|
async getNoteImages() {
|
||||||
return await repository.getEntities("SELECT * FROM note_images WHERE noteId = ? AND isDeleted = 0", [this.noteId]);
|
return await repository.getEntities("SELECT * FROM note_images WHERE noteId = ? AND isDeleted = 0", [this.noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<Branch[]>}
|
||||||
|
*/
|
||||||
async getBranches() {
|
async getBranches() {
|
||||||
return await repository.getEntities("SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ?", [this.noteId]);
|
return await repository.getEntities("SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ?", [this.noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getChildNote(name) {
|
/**
|
||||||
return await repository.getEntity(`
|
* @returns {Promise<Note[]>} child notes of this note
|
||||||
SELECT notes.*
|
*/
|
||||||
FROM branches
|
|
||||||
JOIN notes USING(noteId)
|
|
||||||
WHERE notes.isDeleted = 0
|
|
||||||
AND branches.isDeleted = 0
|
|
||||||
AND branches.parentNoteId = ?
|
|
||||||
AND notes.title = ?`, [this.noteId, name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getChildNotes() {
|
async getChildNotes() {
|
||||||
return await repository.getEntities(`
|
return await repository.getEntities(`
|
||||||
SELECT notes.*
|
SELECT notes.*
|
||||||
@@ -110,6 +482,9 @@ class Note extends Entity {
|
|||||||
ORDER BY branches.notePosition`, [this.noteId]);
|
ORDER BY branches.notePosition`, [this.noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<Branch[]>} child branches of this note
|
||||||
|
*/
|
||||||
async getChildBranches() {
|
async getChildBranches() {
|
||||||
return await repository.getEntities(`
|
return await repository.getEntities(`
|
||||||
SELECT branches.*
|
SELECT branches.*
|
||||||
@@ -119,6 +494,9 @@ class Note extends Entity {
|
|||||||
ORDER BY branches.notePosition`, [this.noteId]);
|
ORDER BY branches.notePosition`, [this.noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<Note[]>} parent notes of this note (note can have multiple parents because of cloning)
|
||||||
|
*/
|
||||||
async getParentNotes() {
|
async getParentNotes() {
|
||||||
return await repository.getEntities(`
|
return await repository.getEntities(`
|
||||||
SELECT parent_notes.*
|
SELECT parent_notes.*
|
||||||
@@ -131,14 +509,12 @@ class Note extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeSaving() {
|
beforeSaving() {
|
||||||
super.beforeSaving();
|
if (this.isJson() && this.jsonContent) {
|
||||||
|
|
||||||
if (this.isJson()) {
|
|
||||||
this.content = JSON.stringify(this.jsonContent, null, '\t');
|
this.content = JSON.stringify(this.jsonContent, null, '\t');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isProtected) {
|
if (this.isProtected) {
|
||||||
protected_session.encryptNote(this);
|
protectedSessionService.encryptNote(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isDeleted) {
|
if (!this.isDeleted) {
|
||||||
@@ -149,7 +525,11 @@ class Note extends Entity {
|
|||||||
this.dateCreated = dateUtils.nowDate();
|
this.dateCreated = dateUtils.nowDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dateModified = dateUtils.nowDate();
|
super.beforeSaving();
|
||||||
|
|
||||||
|
if (this.isChanged) {
|
||||||
|
this.dateModified = dateUtils.nowDate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,22 @@ const Entity = require('./entity');
|
|||||||
const repository = require('../services/repository');
|
const repository = require('../services/repository');
|
||||||
const dateUtils = require('../services/date_utils');
|
const dateUtils = require('../services/date_utils');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents image's placement in the note(s). One image may be placed into several notes.
|
||||||
|
*
|
||||||
|
* @param {string} noteImageId
|
||||||
|
* @param {string} noteId
|
||||||
|
* @param {string} imageId
|
||||||
|
* @param {boolean} isDeleted
|
||||||
|
* @param {string} dateModified
|
||||||
|
* @param {string} dateCreated
|
||||||
|
*
|
||||||
|
* @extends Entity
|
||||||
|
*/
|
||||||
class NoteImage extends Entity {
|
class NoteImage extends Entity {
|
||||||
static get tableName() { return "note_images"; }
|
static get entityName() { return "note_images"; }
|
||||||
static get primaryKeyName() { return "noteImageId"; }
|
static get primaryKeyName() { return "noteImageId"; }
|
||||||
|
static get hashedProperties() { return ["noteImageId", "noteId", "imageId", "isDeleted", "dateCreated"]; }
|
||||||
|
|
||||||
async getNote() {
|
async getNote() {
|
||||||
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
|
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
|
||||||
@@ -17,8 +30,6 @@ class NoteImage extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeSaving() {
|
beforeSaving() {
|
||||||
super.beforeSaving();
|
|
||||||
|
|
||||||
if (!this.isDeleted) {
|
if (!this.isDeleted) {
|
||||||
this.isDeleted = false;
|
this.isDeleted = false;
|
||||||
}
|
}
|
||||||
@@ -27,7 +38,11 @@ class NoteImage extends Entity {
|
|||||||
this.dateCreated = dateUtils.nowDate();
|
this.dateCreated = dateUtils.nowDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dateModified = dateUtils.nowDate();
|
super.beforeSaving();
|
||||||
|
|
||||||
|
if (this.isChanged) {
|
||||||
|
this.dateModified = dateUtils.nowDate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,36 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Entity = require('./entity');
|
const Entity = require('./entity');
|
||||||
const protected_session = require('../services/protected_session');
|
const protectedSessionService = require('../services/protected_session');
|
||||||
const utils = require('../services/utils');
|
|
||||||
const repository = require('../services/repository');
|
const repository = require('../services/repository');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NoteRevision represents snapshot of note's title and content at some point in the past. It's used for seamless note versioning.
|
||||||
|
*
|
||||||
|
* @param {string} noteRevisionId
|
||||||
|
* @param {string} noteId
|
||||||
|
* @param {string} type
|
||||||
|
* @param {string} mime
|
||||||
|
* @param {string} title
|
||||||
|
* @param {string} content
|
||||||
|
* @param {string} isProtected
|
||||||
|
* @param {string} dateModifiedFrom
|
||||||
|
* @param {string} dateModifiedTo
|
||||||
|
*
|
||||||
|
* @extends Entity
|
||||||
|
*/
|
||||||
class NoteRevision extends Entity {
|
class NoteRevision extends Entity {
|
||||||
static get tableName() { return "note_revisions"; }
|
static get entityName() { return "note_revisions"; }
|
||||||
static get primaryKeyName() { return "noteRevisionId"; }
|
static get primaryKeyName() { return "noteRevisionId"; }
|
||||||
|
static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "content", "isProtected", "dateModifiedFrom", "dateModifiedTo"]; }
|
||||||
|
|
||||||
constructor(row) {
|
constructor(row) {
|
||||||
super(row);
|
super(row);
|
||||||
|
|
||||||
|
this.isProtected = !!this.isProtected;
|
||||||
|
|
||||||
if (this.isProtected) {
|
if (this.isProtected) {
|
||||||
protected_session.decryptNoteRevision(this);
|
protectedSessionService.decryptNoteRevision(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,11 +39,11 @@ class NoteRevision extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeSaving() {
|
beforeSaving() {
|
||||||
super.beforeSaving();
|
|
||||||
|
|
||||||
if (this.isProtected) {
|
if (this.isProtected) {
|
||||||
protected_session.encryptNoteRevision(this);
|
protectedSessionService.encryptNoteRevision(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.beforeSaving();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
37
src/entities/option.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const Entity = require('./entity');
|
||||||
|
const dateUtils = require('../services/date_utils');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option represents name-value pair, either directly configurable by the user or some system property.
|
||||||
|
*
|
||||||
|
* @param {string} name
|
||||||
|
* @param {string} value
|
||||||
|
* @param {boolean} isSynced
|
||||||
|
* @param {string} dateModified
|
||||||
|
* @param {string} dateCreated
|
||||||
|
*
|
||||||
|
* @extends Entity
|
||||||
|
*/
|
||||||
|
class Option extends Entity {
|
||||||
|
static get entityName() { return "options"; }
|
||||||
|
static get primaryKeyName() { return "name"; }
|
||||||
|
static get hashedProperties() { return ["name", "value"]; }
|
||||||
|
|
||||||
|
constructor(row) {
|
||||||
|
super(row);
|
||||||
|
|
||||||
|
this.isSynced = !!this.isSynced;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeSaving() {
|
||||||
|
super.beforeSaving();
|
||||||
|
|
||||||
|
if (this.isChanged) {
|
||||||
|
this.dateModified = dateUtils.nowDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Option;
|
||||||
@@ -1,10 +1,34 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Entity = require('./entity');
|
const Entity = require('./entity');
|
||||||
|
const dateUtils = require('../services/date_utils');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecentNote represents recently visited note.
|
||||||
|
*
|
||||||
|
* @param {string} branchId
|
||||||
|
* @param {string} notePath
|
||||||
|
* @param {boolean} isDeleted
|
||||||
|
* @param {string} dateModified
|
||||||
|
*
|
||||||
|
* @extends Entity
|
||||||
|
*/
|
||||||
class RecentNote extends Entity {
|
class RecentNote extends Entity {
|
||||||
static get tableName() { return "recent_notes"; }
|
static get entityName() { return "recent_notes"; }
|
||||||
static get primaryKeyName() { return "branchId"; }
|
static get primaryKeyName() { return "branchId"; }
|
||||||
|
static get hashedProperties() { return ["branchId", "notePath", "dateCreated", "isDeleted"]; }
|
||||||
|
|
||||||
|
beforeSaving() {
|
||||||
|
if (!this.isDeleted) {
|
||||||
|
this.isDeleted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.dateCreated) {
|
||||||
|
this.dateCreated = dateUtils.nowDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.beforeSaving();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = RecentNote;
|
module.exports = RecentNote;
|
||||||
BIN
src/public/images/icons/back-24.png
Normal file
|
After Width: | Height: | Size: 511 B |
BIN
src/public/images/icons/clock-16.png
Normal file
|
After Width: | Height: | Size: 381 B |
|
Before Width: | Height: | Size: 245 B After Width: | Height: | Size: 245 B |
|
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 339 B |
|
Before Width: | Height: | Size: 463 B After Width: | Height: | Size: 463 B |
BIN
src/public/images/icons/edit-20.png
Normal file
|
After Width: | Height: | Size: 312 B |
|
Before Width: | Height: | Size: 288 B After Width: | Height: | Size: 288 B |
|
Before Width: | Height: | Size: 284 B After Width: | Height: | Size: 284 B |
|
Before Width: | Height: | Size: 292 B After Width: | Height: | Size: 292 B |
BIN
src/public/images/icons/forward-24.png
Normal file
|
After Width: | Height: | Size: 511 B |
|
Before Width: | Height: | Size: 155 B After Width: | Height: | Size: 155 B |
|
Before Width: | Height: | Size: 323 B |
|
Before Width: | Height: | Size: 358 B After Width: | Height: | Size: 358 B |
|
Before Width: | Height: | Size: 252 B After Width: | Height: | Size: 252 B |
BIN
src/public/images/icons/play-20.png
Normal file
|
After Width: | Height: | Size: 288 B |
BIN
src/public/images/icons/save-20.png
Normal file
|
After Width: | Height: | Size: 388 B |
BIN
src/public/images/icons/search-20.png
Normal file
|
After Width: | Height: | Size: 431 B |
|
Before Width: | Height: | Size: 419 B After Width: | Height: | Size: 419 B |
|
Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 354 B |
BIN
src/public/images/icons/shield-20.png
Normal file
|
After Width: | Height: | Size: 388 B |
BIN
src/public/images/icons/shield-off-20.png
Normal file
|
After Width: | Height: | Size: 462 B |
BIN
src/public/images/icons/tree-root-16.png
Normal file
|
After Width: | Height: | Size: 240 B |
|
Before Width: | Height: | Size: 337 B |
BIN
src/public/images/icons/x-20.png
Normal file
|
After Width: | Height: | Size: 259 B |
1
src/public/images/shield.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fafafa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shield"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 274 B |
1
src/public/images/trilium.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path d="M63.966,45.043c0.008-0.009,0.021-0.021,0.027-0.029c0.938-1.156-0.823-13.453-5.063-20.125 c-1.389-2.186-2.239-3.423-3.219-4.719c-3.907-5.166-6-6.125-6-6.125S35.732,24.78,36.149,44.315 c-1.754,0.065-11.218,7.528-14.826,14.388c-1.206,2.291-1.856,3.645-2.493,5.141c-2.539,5.957-2.33,8.25-2.33,8.25 s16.271,6.79,33.014-3.294c0.007,0.021,0.013,0.046,0.02,0.063c0.537,1.389,12.08,5.979,19.976,5.621 c2.587-0.116,4.084-0.238,5.696-0.444c6.424-0.818,8.298-2.157,8.298-2.157S81.144,54.396,63.966,45.043z M50.787,65.343 c1.059-1.183,4.648-5.853,0.995-11.315c-0.253-0.377-0.496-0.236-0.496-0.236s0.063,10.822-5.162,12.359 c-5.225,1.537-13.886,4.4-20.427,0.455C25,66.186,26.924,53.606,38.544,47.229c0.546,1.599,2.836,6.854,9.292,6.409 c0.453-0.031,0.453-0.313,0.453-0.313s-9.422-5.328-8.156-10.625s3.089-14.236,9.766-17.948c0.714-0.397,10.746,7.593,10.417,20.94 c-1.606-0.319-7.377-1.004-10.226,4.864c-0.198,0.409,0.046,0.549,0.046,0.549s9.31-5.521,13.275-1.789 c3.965,3.733,10.813,9.763,10.71,17.4C74.111,67.533,62.197,72.258,50.787,65.343z M35.613,35.145c0,0-0.991,3.241-0.603,7.524 l-13.393-7.524C21.618,35.145,27.838,30.931,35.613,35.145z M21.193,36.03l13.344,7.612c-3.872,1.872-6.142,4.388-6.142,4.388 C20.78,43.531,21.193,36.03,21.193,36.03z M72.287,49.064c0,0-2.321-2.471-6.23-4.263l13.187-7.881 C79.243,36.92,79.808,44.413,72.287,49.064z M78.687,36.113l-13.237,7.794c0.3-4.291-0.754-7.511-0.754-7.511 C72.383,32.025,78.687,36.113,78.687,36.113z M42.076,73.778c0,0,3.309-0.737,6.845-3.185l0.056,15.361 C48.977,85.955,42.244,82.621,42.076,73.778z M49.956,85.888L50,70.526c3.539,2.445,6.846,3.181,6.846,3.181 C56.686,82.551,49.956,85.888,49.956,85.888z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -2,7 +2,8 @@ import cloningService from '../services/cloning.js';
|
|||||||
import linkService from '../services/link.js';
|
import linkService from '../services/link.js';
|
||||||
import noteDetailService from '../services/note_detail.js';
|
import noteDetailService from '../services/note_detail.js';
|
||||||
import treeUtils from '../services/tree_utils.js';
|
import treeUtils from '../services/tree_utils.js';
|
||||||
import autocompleteService from '../services/autocomplete.js';
|
import noteDetailText from "../services/note_detail_text.js";
|
||||||
|
import noteAutocompleteService from "../services/note_autocomplete.js";
|
||||||
|
|
||||||
const $dialog = $("#add-link-dialog");
|
const $dialog = $("#add-link-dialog");
|
||||||
const $form = $("#add-link-form");
|
const $form = $("#add-link-form");
|
||||||
@@ -11,8 +12,10 @@ const $linkTitle = $("#link-title");
|
|||||||
const $clonePrefix = $("#clone-prefix");
|
const $clonePrefix = $("#clone-prefix");
|
||||||
const $linkTitleFormGroup = $("#add-link-title-form-group");
|
const $linkTitleFormGroup = $("#add-link-title-form-group");
|
||||||
const $prefixFormGroup = $("#add-link-prefix-form-group");
|
const $prefixFormGroup = $("#add-link-prefix-form-group");
|
||||||
|
const $linkTypeDiv = $("#add-link-type-div");
|
||||||
const $linkTypes = $("input[name='add-link-type']");
|
const $linkTypes = $("input[name='add-link-type']");
|
||||||
const $linkTypeHtml = $linkTypes.filter('input[value="html"]');
|
const $linkTypeHtml = $linkTypes.filter('input[value="html"]');
|
||||||
|
const $showRecentNotesButton = $dialog.find(".show-recent-notes-button");
|
||||||
|
|
||||||
function setLinkType(linkType) {
|
function setLinkType(linkType) {
|
||||||
$linkTypes.each(function () {
|
$linkTypes.each(function () {
|
||||||
@@ -45,18 +48,22 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
$autoComplete.autocomplete({
|
await $autoComplete.autocomplete({
|
||||||
source: await autocompleteService.getAutocompleteItems(),
|
source: noteAutocompleteService.autocompleteSource,
|
||||||
minLength: 0,
|
minLength: 0,
|
||||||
change: () => {
|
change: async (event, ui) => {
|
||||||
const val = $autoComplete.val();
|
if (!ui.item) {
|
||||||
const notePath = linkService.getNodePathFromLabel(val);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notePath = linkService.getNotePathFromLabel(ui.item.value);
|
||||||
|
|
||||||
if (!notePath) {
|
if (!notePath) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -64,24 +71,33 @@ async function showDialog() {
|
|||||||
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
||||||
|
|
||||||
if (noteId) {
|
if (noteId) {
|
||||||
setDefaultLinkTitle(noteId);
|
await setDefaultLinkTitle(noteId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
select: function (event, ui) {
|
||||||
|
if (ui.item.value === 'No results') {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 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.getNotePathFromLabel(ui.item.value);
|
||||||
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
||||||
|
|
||||||
setDefaultLinkTitle(noteId);
|
await setDefaultLinkTitle(noteId);
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
showRecentNotes();
|
||||||
}
|
}
|
||||||
|
|
||||||
$form.submit(() => {
|
$form.submit(() => {
|
||||||
const value = $autoComplete.val();
|
const value = $autoComplete.val();
|
||||||
|
|
||||||
const notePath = linkService.getNodePathFromLabel(value);
|
const notePath = linkService.getNotePathFromLabel(value);
|
||||||
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
||||||
|
|
||||||
if (notePath) {
|
if (notePath) {
|
||||||
@@ -92,7 +108,16 @@ $form.submit(() => {
|
|||||||
|
|
||||||
$dialog.dialog("close");
|
$dialog.dialog("close");
|
||||||
|
|
||||||
linkService.addLinkToEditor(linkTitle, '#' + notePath);
|
const linkHref = '#' + notePath;
|
||||||
|
|
||||||
|
if (hasSelection()) {
|
||||||
|
const editor = noteDetailText.getEditor();
|
||||||
|
|
||||||
|
editor.execute('link', linkHref);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
linkService.addLinkToEditor(linkTitle, linkHref);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (linkType === 'selected-to-current') {
|
else if (linkType === 'selected-to-current') {
|
||||||
const prefix = $clonePrefix.val();
|
const prefix = $clonePrefix.val();
|
||||||
@@ -113,21 +138,31 @@ $form.submit(() => {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// returns true if user selected some text, false if there's no selection
|
||||||
|
function hasSelection() {
|
||||||
|
const model = noteDetailText.getEditor().model;
|
||||||
|
const selection = model.document.selection;
|
||||||
|
|
||||||
|
return !selection.isCollapsed;
|
||||||
|
}
|
||||||
|
|
||||||
function linkTypeChanged() {
|
function linkTypeChanged() {
|
||||||
const value = $linkTypes.filter(":checked").val();
|
const value = $linkTypes.filter(":checked").val();
|
||||||
|
|
||||||
if (value === 'html') {
|
$linkTitleFormGroup.toggle(!hasSelection() && value === 'html');
|
||||||
$linkTitleFormGroup.show();
|
$prefixFormGroup.toggle(!hasSelection() && value !== 'html');
|
||||||
$prefixFormGroup.hide();
|
|
||||||
}
|
$linkTypeDiv.toggle(!hasSelection());
|
||||||
else {
|
}
|
||||||
$linkTitleFormGroup.hide();
|
|
||||||
$prefixFormGroup.show();
|
function showRecentNotes() {
|
||||||
}
|
$autoComplete.autocomplete("search", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
$linkTypes.change(linkTypeChanged);
|
$linkTypes.change(linkTypeChanged);
|
||||||
|
|
||||||
|
$showRecentNotesButton.click(showRecentNotes);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
showDialog
|
showDialog
|
||||||
};
|
};
|
||||||
302
src/public/javascripts/dialogs/attributes.js
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
import noteDetailService from '../services/note_detail.js';
|
||||||
|
import server from '../services/server.js';
|
||||||
|
import infoService from "../services/info.js";
|
||||||
|
import treeUtils from "../services/tree_utils.js";
|
||||||
|
import linkService from "../services/link.js";
|
||||||
|
|
||||||
|
const $dialog = $("#attributes-dialog");
|
||||||
|
const $saveAttributesButton = $("#save-attributes-button");
|
||||||
|
const $ownedAttributesBody = $('#owned-attributes-table tbody');
|
||||||
|
|
||||||
|
const attributesModel = new AttributesModel();
|
||||||
|
|
||||||
|
function AttributesModel() {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
this.ownedAttributes = ko.observableArray();
|
||||||
|
this.inheritedAttributes = ko.observableArray();
|
||||||
|
|
||||||
|
this.availableTypes = [
|
||||||
|
{ text: "Label", value: "label" },
|
||||||
|
{ text: "Label definition", value: "label-definition" },
|
||||||
|
{ text: "Relation", value: "relation" },
|
||||||
|
{ text: "Relation definition", value: "relation-definition" }
|
||||||
|
];
|
||||||
|
|
||||||
|
this.availableLabelTypes = [
|
||||||
|
{ text: "Text", value: "text" },
|
||||||
|
{ text: "Number", value: "number" },
|
||||||
|
{ text: "Boolean", value: "boolean" },
|
||||||
|
{ text: "Date", value: "date" },
|
||||||
|
{ text: "URL", value: "url"}
|
||||||
|
];
|
||||||
|
|
||||||
|
this.multiplicityTypes = [
|
||||||
|
{ text: "Single value", value: "singlevalue" },
|
||||||
|
{ text: "Multi value", value: "multivalue" }
|
||||||
|
];
|
||||||
|
|
||||||
|
this.typeChanged = function(data, event) {
|
||||||
|
self.getTargetAttribute(event.target).valueHasMutated();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.labelTypeChanged = function(data, event) {
|
||||||
|
self.getTargetAttribute(event.target).valueHasMutated();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.updateAttributePositions = function() {
|
||||||
|
let position = 0;
|
||||||
|
|
||||||
|
// we need to update positions by searching in the DOM, because order of the
|
||||||
|
// attributes in the viewmodel (self.ownedAttributes()) stays the same
|
||||||
|
$ownedAttributesBody.find('input[name="position"]').each(function() {
|
||||||
|
const attribute = self.getTargetAttribute(this);
|
||||||
|
|
||||||
|
attribute().position = position++;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function showAttributes(attributes) {
|
||||||
|
const ownedAttributes = attributes.filter(attr => attr.isOwned);
|
||||||
|
|
||||||
|
for (const attr of ownedAttributes) {
|
||||||
|
attr.labelValue = attr.type === 'label' ? attr.value : '';
|
||||||
|
attr.relationValue = attr.type === 'relation' ? (await treeUtils.getNoteTitle(attr.value) + " (" + attr.value + ")") : '';
|
||||||
|
attr.labelDefinition = (attr.type === 'label-definition' && attr.value) ? attr.value : {
|
||||||
|
labelType: "text",
|
||||||
|
multiplicityType: "singlevalue",
|
||||||
|
isPromoted: true
|
||||||
|
};
|
||||||
|
|
||||||
|
attr.relationDefinition = (attr.type === 'relation-definition' && attr.value) ? attr.value : {
|
||||||
|
multiplicityType: "singlevalue",
|
||||||
|
isPromoted: true
|
||||||
|
};
|
||||||
|
|
||||||
|
delete attr.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ownedAttributes(ownedAttributes.map(ko.observable));
|
||||||
|
|
||||||
|
addLastEmptyRow();
|
||||||
|
|
||||||
|
const inheritedAttributes = attributes.filter(attr => !attr.isOwned);
|
||||||
|
|
||||||
|
self.inheritedAttributes(inheritedAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadAttributes = async function() {
|
||||||
|
const noteId = noteDetailService.getCurrentNoteId();
|
||||||
|
|
||||||
|
const attributes = await server.get('notes/' + noteId + '/attributes');
|
||||||
|
|
||||||
|
await showAttributes(attributes);
|
||||||
|
|
||||||
|
// attribute might not be rendered immediatelly so could not focus
|
||||||
|
setTimeout(() => $(".attribute-type-select:last").focus(), 100);
|
||||||
|
|
||||||
|
$ownedAttributesBody.sortable({
|
||||||
|
handle: '.handle',
|
||||||
|
containment: $ownedAttributesBody,
|
||||||
|
update: this.updateAttributePositions
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.deleteAttribute = function(data, event) {
|
||||||
|
const attribute = self.getTargetAttribute(event.target);
|
||||||
|
const attributeData = attribute();
|
||||||
|
|
||||||
|
if (attributeData) {
|
||||||
|
attributeData.isDeleted = true;
|
||||||
|
|
||||||
|
attribute(attributeData);
|
||||||
|
|
||||||
|
addLastEmptyRow();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function isValid() {
|
||||||
|
for (let attributes = self.ownedAttributes(), i = 0; i < attributes.length; i++) {
|
||||||
|
if (self.isEmptyName(i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.save = async function() {
|
||||||
|
// we need to defocus from input (in case of enter-triggered save) because value is updated
|
||||||
|
// on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
|
||||||
|
// stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
|
||||||
|
$saveAttributesButton.focus();
|
||||||
|
|
||||||
|
if (!isValid()) {
|
||||||
|
alert("Please fix all validation errors and try saving again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateAttributePositions();
|
||||||
|
|
||||||
|
const noteId = noteDetailService.getCurrentNoteId();
|
||||||
|
|
||||||
|
const attributesToSave = self.ownedAttributes()
|
||||||
|
.map(attribute => attribute())
|
||||||
|
.filter(attribute => attribute.attributeId !== "" || attribute.name !== "");
|
||||||
|
|
||||||
|
for (const attr of attributesToSave) {
|
||||||
|
if (attr.type === 'label') {
|
||||||
|
attr.value = attr.labelValue;
|
||||||
|
}
|
||||||
|
else if (attr.type === 'relation') {
|
||||||
|
attr.value = treeUtils.getNoteIdFromNotePath(linkService.getNotePathFromLabel(attr.relationValue));
|
||||||
|
}
|
||||||
|
else if (attr.type === 'label-definition') {
|
||||||
|
attr.value = attr.labelDefinition;
|
||||||
|
}
|
||||||
|
else if (attr.type === 'relation-definition') {
|
||||||
|
attr.value = attr.relationDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete attr.labelValue;
|
||||||
|
delete attr.relationValue;
|
||||||
|
delete attr.labelDefinition;
|
||||||
|
delete attr.relationDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributes = await server.put('notes/' + noteId + '/attributes', attributesToSave);
|
||||||
|
|
||||||
|
await showAttributes(attributes);
|
||||||
|
|
||||||
|
infoService.showMessage("Attributes have been saved.");
|
||||||
|
|
||||||
|
noteDetailService.loadAttributes();
|
||||||
|
};
|
||||||
|
|
||||||
|
function addLastEmptyRow() {
|
||||||
|
const attributes = self.ownedAttributes().filter(attr => !attr().isDeleted);
|
||||||
|
const last = attributes.length === 0 ? null : attributes[attributes.length - 1]();
|
||||||
|
|
||||||
|
if (!last || last.name.trim() !== "") {
|
||||||
|
self.ownedAttributes.push(ko.observable({
|
||||||
|
attributeId: '',
|
||||||
|
type: 'label',
|
||||||
|
name: '',
|
||||||
|
labelValue: '',
|
||||||
|
relationValue: '',
|
||||||
|
isInheritable: false,
|
||||||
|
isDeleted: false,
|
||||||
|
position: 0,
|
||||||
|
labelDefinition: {
|
||||||
|
labelType: "text",
|
||||||
|
multiplicityType: "singlevalue",
|
||||||
|
isPromoted: true
|
||||||
|
},
|
||||||
|
relationDefinition: {
|
||||||
|
multiplicityType: "singlevalue",
|
||||||
|
isPromoted: true
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.attributeChanged = function (data, event) {
|
||||||
|
addLastEmptyRow();
|
||||||
|
|
||||||
|
const attribute = self.getTargetAttribute(event.target);
|
||||||
|
|
||||||
|
attribute.valueHasMutated();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isEmptyName = function(index) {
|
||||||
|
const cur = self.ownedAttributes()[index]();
|
||||||
|
|
||||||
|
return cur.name.trim() === "" && !cur.isDeleted && (cur.attributeId !== "" || cur.labelValue !== "" || cur.relationValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getTargetAttribute = function(target) {
|
||||||
|
const context = ko.contextFor(target);
|
||||||
|
const index = context.$index();
|
||||||
|
|
||||||
|
return self.ownedAttributes()[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showDialog() {
|
||||||
|
glob.activeDialog = $dialog;
|
||||||
|
|
||||||
|
await attributesModel.loadAttributes();
|
||||||
|
|
||||||
|
$dialog.dialog({
|
||||||
|
modal: true,
|
||||||
|
width: 950,
|
||||||
|
height: 700
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ko.applyBindings(attributesModel, $dialog[0]);
|
||||||
|
|
||||||
|
$dialog.on('focus', '.attribute-name', function (e) {
|
||||||
|
if (!$(this).hasClass("ui-autocomplete-input")) {
|
||||||
|
$(this).autocomplete({
|
||||||
|
source: async (request, response) => {
|
||||||
|
const attribute = attributesModel.getTargetAttribute(this);
|
||||||
|
const type = (attribute().type === 'relation' || attribute().type === 'relation-definition') ? 'relation' : 'label';
|
||||||
|
const names = await server.get('attributes/names/?type=' + type + '&query=' + encodeURIComponent(request.term));
|
||||||
|
const result = names.map(name => {
|
||||||
|
return {
|
||||||
|
label: name,
|
||||||
|
value: name
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.length > 0) {
|
||||||
|
response(result);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
response([{
|
||||||
|
label: "No results",
|
||||||
|
value: "No results"
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
minLength: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(this).autocomplete("search", $(this).val());
|
||||||
|
});
|
||||||
|
|
||||||
|
$dialog.on('focus', '.label-value', async function (e) {
|
||||||
|
if (!$(this).hasClass("ui-autocomplete-input")) {
|
||||||
|
const attributeName = $(this).parent().parent().find('.attribute-name').val();
|
||||||
|
|
||||||
|
if (attributeName.trim() === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributeValues = await server.get('attributes/values/' + encodeURIComponent(attributeName));
|
||||||
|
|
||||||
|
if (attributeValues.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(this).autocomplete({
|
||||||
|
// shouldn't be required and autocomplete should just accept array of strings, but that fails
|
||||||
|
// because we have overriden filter() function in autocomplete.js
|
||||||
|
source: attributeValues.map(attribute => {
|
||||||
|
return {
|
||||||
|
attribute: attribute,
|
||||||
|
value: attribute
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
minLength: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(this).autocomplete("search", $(this).val());
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
showDialog
|
||||||
|
};
|
||||||
@@ -15,7 +15,7 @@ async function showDialog() {
|
|||||||
|
|
||||||
await $dialog.dialog({
|
await $dialog.dialog({
|
||||||
modal: true,
|
modal: true,
|
||||||
width: 500
|
width: 600
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentNode = treeService.getCurrentNode();
|
const currentNode = treeService.getCurrentNode();
|
||||||
@@ -25,7 +25,7 @@ async function showDialog() {
|
|||||||
|
|
||||||
$treePrefixInput.val(branch.prefix).focus();
|
$treePrefixInput.val(branch.prefix).focus();
|
||||||
|
|
||||||
const noteTitle = treeUtils.getNoteTitle(currentNode.data.noteId);
|
const noteTitle = await treeUtils.getNoteTitle(currentNode.data.noteId);
|
||||||
|
|
||||||
$noteTitle.html(noteTitle);
|
$noteTitle.html(noteTitle);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ async function showDialog() {
|
|||||||
$list.html('');
|
$list.html('');
|
||||||
|
|
||||||
for (const event of result) {
|
for (const event of result) {
|
||||||
const dateTime = utils.formatDateTime(utils.parseDate(event.dateAdded));
|
const dateTime = utils.formatDateTime(utils.parseDate(event.dateCreated));
|
||||||
|
|
||||||
if (event.noteId) {
|
if (event.noteId) {
|
||||||
const noteLink = linkService.createNoteLink(event.noteId).prop('outerHTML');
|
const noteLink = await linkService.createNoteLink(event.noteId).prop('outerHTML');
|
||||||
|
|
||||||
event.comment = event.comment.replace('<note>', noteLink);
|
event.comment = event.comment.replace('<note>', noteLink);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import treeService from '../services/tree.js';
|
import treeService from '../services/tree.js';
|
||||||
import linkService from '../services/link.js';
|
import searchNotesService from '../services/search_notes.js';
|
||||||
import utils from '../services/utils.js';
|
import noteautocompleteService from '../services/note_autocomplete.js';
|
||||||
import autocompleteService from '../services/autocomplete.js';
|
import linkService from "../services/link.js";
|
||||||
|
|
||||||
const $dialog = $("#jump-to-note-dialog");
|
const $dialog = $("#jump-to-note-dialog");
|
||||||
const $autoComplete = $("#jump-to-note-autocomplete");
|
const $autoComplete = $("#jump-to-note-autocomplete");
|
||||||
const $form = $("#jump-to-note-form");
|
const $showInFullTextButton = $("#show-in-full-text-button");
|
||||||
|
const $showRecentNotesButton = $dialog.find(".show-recent-notes-button");
|
||||||
|
|
||||||
async function showDialog() {
|
async function showDialog() {
|
||||||
glob.activeDialog = $dialog;
|
glob.activeDialog = $dialog;
|
||||||
@@ -14,35 +15,54 @@ async function showDialog() {
|
|||||||
|
|
||||||
$dialog.dialog({
|
$dialog.dialog({
|
||||||
modal: true,
|
modal: true,
|
||||||
width: 800
|
width: 800,
|
||||||
|
position: { my: "center top+100", at: "top", of: window }
|
||||||
});
|
});
|
||||||
|
|
||||||
await $autoComplete.autocomplete({
|
await $autoComplete.autocomplete({
|
||||||
source: await utils.stopWatch("building autocomplete", autocompleteService.getAutocompleteItems),
|
source: noteautocompleteService.autocompleteSource,
|
||||||
minLength: 1
|
focus: event => event.preventDefault(),
|
||||||
|
minLength: 0,
|
||||||
|
autoFocus: true,
|
||||||
|
select: function (event, ui) {
|
||||||
|
if (ui.item.value === 'No results') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notePath = linkService.getNotePathFromLabel(ui.item.value);
|
||||||
|
|
||||||
|
treeService.activateNote(notePath);
|
||||||
|
|
||||||
|
$dialog.dialog('close');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
showRecentNotes();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelectedNotePath() {
|
function showInFullText(e) {
|
||||||
const val = $autoComplete.val();
|
// stop from propagating upwards (dangerous especially with ctrl+enter executable javascript notes)
|
||||||
return linkService.getNodePathFromLabel(val);
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const searchText = $autoComplete.val();
|
||||||
|
|
||||||
|
searchNotesService.resetSearch();
|
||||||
|
searchNotesService.showSearch();
|
||||||
|
searchNotesService.doSearch(searchText);
|
||||||
|
|
||||||
|
$dialog.dialog('close');
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToNote() {
|
function showRecentNotes() {
|
||||||
const notePath = getSelectedNotePath();
|
$autoComplete.autocomplete("search", "");
|
||||||
|
|
||||||
if (notePath) {
|
|
||||||
treeService.activateNode(notePath);
|
|
||||||
|
|
||||||
$dialog.dialog('close');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$form.submit(() => {
|
$showInFullTextButton.click(showInFullText);
|
||||||
goToNote();
|
|
||||||
|
|
||||||
return false;
|
$showRecentNotesButton.click(showRecentNotes);
|
||||||
});
|
|
||||||
|
$dialog.bind('keydown', 'ctrl+return', showInFullText);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
showDialog
|
showDialog
|
||||||
|
|||||||
@@ -1,223 +0,0 @@
|
|||||||
import noteDetailService from '../services/note_detail.js';
|
|
||||||
import utils from '../services/utils.js';
|
|
||||||
import server from '../services/server.js';
|
|
||||||
import infoService from "../services/info.js";
|
|
||||||
|
|
||||||
const $dialog = $("#labels-dialog");
|
|
||||||
const $saveLabelsButton = $("#save-labels-button");
|
|
||||||
const $labelsBody = $('#labels-table tbody');
|
|
||||||
|
|
||||||
const labelsModel = new LabelsModel();
|
|
||||||
let labelNames = [];
|
|
||||||
|
|
||||||
function LabelsModel() {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
this.labels = ko.observableArray();
|
|
||||||
|
|
||||||
this.updateLabelPositions = function() {
|
|
||||||
let position = 0;
|
|
||||||
|
|
||||||
// we need to update positions by searching in the DOM, because order of the
|
|
||||||
// labels in the viewmodel (self.labels()) stays the same
|
|
||||||
$labelsBody.find('input[name="position"]').each(function() {
|
|
||||||
const label = self.getTargetLabel(this);
|
|
||||||
|
|
||||||
label().position = position++;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.loadLabels = async function() {
|
|
||||||
const noteId = noteDetailService.getCurrentNoteId();
|
|
||||||
|
|
||||||
const labels = await server.get('notes/' + noteId + '/labels');
|
|
||||||
|
|
||||||
self.labels(labels.map(ko.observable));
|
|
||||||
|
|
||||||
addLastEmptyRow();
|
|
||||||
|
|
||||||
labelNames = await server.get('labels/names');
|
|
||||||
|
|
||||||
// label might not be rendered immediatelly so could not focus
|
|
||||||
setTimeout(() => $(".label-name:last").focus(), 100);
|
|
||||||
|
|
||||||
$labelsBody.sortable({
|
|
||||||
handle: '.handle',
|
|
||||||
containment: $labelsBody,
|
|
||||||
update: this.updateLabelPositions
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.deleteLabel = function(data, event) {
|
|
||||||
const label = self.getTargetLabel(event.target);
|
|
||||||
const labelData = label();
|
|
||||||
|
|
||||||
if (labelData) {
|
|
||||||
labelData.isDeleted = 1;
|
|
||||||
|
|
||||||
label(labelData);
|
|
||||||
|
|
||||||
addLastEmptyRow();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function isValid() {
|
|
||||||
for (let labels = self.labels(), i = 0; i < labels.length; i++) {
|
|
||||||
if (self.isEmptyName(i)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.save = async function() {
|
|
||||||
// we need to defocus from input (in case of enter-triggered save) because value is updated
|
|
||||||
// on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
|
|
||||||
// stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
|
|
||||||
$saveLabelsButton.focus();
|
|
||||||
|
|
||||||
if (!isValid()) {
|
|
||||||
alert("Please fix all validation errors and try saving again.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.updateLabelPositions();
|
|
||||||
|
|
||||||
const noteId = noteDetailService.getCurrentNoteId();
|
|
||||||
|
|
||||||
const labelsToSave = self.labels()
|
|
||||||
.map(label => label())
|
|
||||||
.filter(label => label.labelId !== "" || label.name !== "");
|
|
||||||
|
|
||||||
const labels = await server.put('notes/' + noteId + '/labels', labelsToSave);
|
|
||||||
|
|
||||||
self.labels(labels.map(ko.observable));
|
|
||||||
|
|
||||||
addLastEmptyRow();
|
|
||||||
|
|
||||||
infoService.showMessage("Labels have been saved.");
|
|
||||||
|
|
||||||
noteDetailService.loadLabelList();
|
|
||||||
};
|
|
||||||
|
|
||||||
function addLastEmptyRow() {
|
|
||||||
const labels = self.labels().filter(attr => attr().isDeleted === 0);
|
|
||||||
const last = labels.length === 0 ? null : labels[labels.length - 1]();
|
|
||||||
|
|
||||||
if (!last || last.name.trim() !== "" || last.value !== "") {
|
|
||||||
self.labels.push(ko.observable({
|
|
||||||
labelId: '',
|
|
||||||
name: '',
|
|
||||||
value: '',
|
|
||||||
isDeleted: 0,
|
|
||||||
position: 0
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.labelChanged = function (data, event) {
|
|
||||||
addLastEmptyRow();
|
|
||||||
|
|
||||||
const label = self.getTargetLabel(event.target);
|
|
||||||
|
|
||||||
label.valueHasMutated();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.isNotUnique = function(index) {
|
|
||||||
const cur = self.labels()[index]();
|
|
||||||
|
|
||||||
if (cur.name.trim() === "") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let labels = self.labels(), i = 0; i < labels.length; i++) {
|
|
||||||
const label = labels[i]();
|
|
||||||
|
|
||||||
if (index !== i && cur.name === label.name) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.isEmptyName = function(index) {
|
|
||||||
const cur = self.labels()[index]();
|
|
||||||
|
|
||||||
return cur.name.trim() === "" && (cur.labelId !== "" || cur.value !== "");
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getTargetLabel = function(target) {
|
|
||||||
const context = ko.contextFor(target);
|
|
||||||
const index = context.$index();
|
|
||||||
|
|
||||||
return self.labels()[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function showDialog() {
|
|
||||||
glob.activeDialog = $dialog;
|
|
||||||
|
|
||||||
await labelsModel.loadLabels();
|
|
||||||
|
|
||||||
$dialog.dialog({
|
|
||||||
modal: true,
|
|
||||||
width: 800,
|
|
||||||
height: 500
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ko.applyBindings(labelsModel, document.getElementById('labels-dialog'));
|
|
||||||
|
|
||||||
$(document).on('focus', '.label-name', function (e) {
|
|
||||||
if (!$(this).hasClass("ui-autocomplete-input")) {
|
|
||||||
$(this).autocomplete({
|
|
||||||
// shouldn't be required and autocomplete should just accept array of strings, but that fails
|
|
||||||
// because we have overriden filter() function in autocomplete.js
|
|
||||||
source: labelNames.map(label => {
|
|
||||||
return {
|
|
||||||
label: label,
|
|
||||||
value: label
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
minLength: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$(this).autocomplete("search", $(this).val());
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).on('focus', '.label-value', async function (e) {
|
|
||||||
if (!$(this).hasClass("ui-autocomplete-input")) {
|
|
||||||
const labelName = $(this).parent().parent().find('.label-name').val();
|
|
||||||
|
|
||||||
if (labelName.trim() === "") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const labelValues = await server.get('labels/values/' + encodeURIComponent(labelName));
|
|
||||||
|
|
||||||
if (labelValues.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$(this).autocomplete({
|
|
||||||
// shouldn't be required and autocomplete should just accept array of strings, but that fails
|
|
||||||
// because we have overriden filter() function in autocomplete.js
|
|
||||||
source: labelValues.map(label => {
|
|
||||||
return {
|
|
||||||
label: label,
|
|
||||||
value: label
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
minLength: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$(this).autocomplete("search", $(this).val());
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
|
||||||
showDialog
|
|
||||||
};
|
|
||||||
@@ -54,13 +54,19 @@ $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[data-action='note-revision']", event => {
|
||||||
const linkEl = $(event.target);
|
const linkEl = $(event.target);
|
||||||
const noteId = linkEl.attr('note-path');
|
const noteId = linkEl.attr('data-note-path');
|
||||||
const noteRevisionId = linkEl.attr('note-revision-id');
|
const noteRevisionId = linkEl.attr('data-note-revision-id');
|
||||||
|
|
||||||
showNoteRevisionsDialog(noteId, noteRevisionId);
|
showNoteRevisionsDialog(noteId, noteRevisionId);
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ function formatNode(node, level) {
|
|||||||
const indentAfter = new Array(level - 1).join(' ');
|
const indentAfter = new Array(level - 1).join(' ');
|
||||||
let textNode;
|
let textNode;
|
||||||
|
|
||||||
for (const i = 0; i < node.children.length; i++) {
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
textNode = document.createTextNode('\n' + indentBefore);
|
textNode = document.createTextNode('\n' + indentBefore);
|
||||||
node.insertBefore(textNode, node.children[i]);
|
node.insertBefore(textNode, node.children[i]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import protectedSessionHolder from '../services/protected_session_holder.js';
|
import protectedSessionHolder from '../services/protected_session_holder.js';
|
||||||
import utils from '../services/utils.js';
|
|
||||||
import server from '../services/server.js';
|
import server from '../services/server.js';
|
||||||
import infoService from "../services/info.js";
|
import infoService from "../services/info.js";
|
||||||
|
import zoomService from "../services/zoom.js";
|
||||||
|
import utils from "../services/utils.js";
|
||||||
|
|
||||||
const $dialog = $("#options-dialog");
|
const $dialog = $("#options-dialog");
|
||||||
const $tabs = $("#options-tabs");
|
const $tabs = $("#options-tabs");
|
||||||
@@ -33,8 +34,8 @@ async function showDialog() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveOptions(optionName, optionValue) {
|
async function saveOptions(options) {
|
||||||
await server.put('options/' + encodeURIComponent(optionName) + '/' + encodeURIComponent(optionValue));
|
await server.put('options', options);
|
||||||
|
|
||||||
infoService.showMessage("Options change have been saved.");
|
infoService.showMessage("Options change have been saved.");
|
||||||
}
|
}
|
||||||
@@ -44,6 +45,41 @@ export default {
|
|||||||
saveOptions
|
saveOptions
|
||||||
};
|
};
|
||||||
|
|
||||||
|
addTabHandler((function() {
|
||||||
|
const $themeSelect = $("#theme-select");
|
||||||
|
const $zoomFactorSelect = $("#zoom-factor-select");
|
||||||
|
const $html = $("html");
|
||||||
|
|
||||||
|
function optionsLoaded(options) {
|
||||||
|
$themeSelect.val(options.theme);
|
||||||
|
|
||||||
|
if (utils.isElectron()) {
|
||||||
|
$zoomFactorSelect.val(options.zoomFactor);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$zoomFactorSelect.prop('disabled', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$themeSelect.change(function() {
|
||||||
|
const newTheme = $(this).val();
|
||||||
|
|
||||||
|
$html.attr("class", "theme-" + newTheme);
|
||||||
|
|
||||||
|
server.put('options/theme/' + newTheme);
|
||||||
|
});
|
||||||
|
|
||||||
|
$zoomFactorSelect.change(function() {
|
||||||
|
const newZoomFactor = $(this).val();
|
||||||
|
|
||||||
|
zoomService.setZoomFactorAndSave(newZoomFactor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
optionsLoaded
|
||||||
|
};
|
||||||
|
})());
|
||||||
|
|
||||||
addTabHandler((function() {
|
addTabHandler((function() {
|
||||||
const $form = $("#change-password-form");
|
const $form = $("#change-password-form");
|
||||||
const $oldPassword = $("#old-password");
|
const $oldPassword = $("#old-password");
|
||||||
@@ -93,16 +129,15 @@ addTabHandler((function() {
|
|||||||
addTabHandler((function() {
|
addTabHandler((function() {
|
||||||
const $form = $("#protected-session-timeout-form");
|
const $form = $("#protected-session-timeout-form");
|
||||||
const $protectedSessionTimeout = $("#protected-session-timeout-in-seconds");
|
const $protectedSessionTimeout = $("#protected-session-timeout-in-seconds");
|
||||||
const optionName = 'protectedSessionTimeout';
|
|
||||||
|
|
||||||
function optionsLoaded(options) {
|
function optionsLoaded(options) {
|
||||||
$protectedSessionTimeout.val(options[optionName]);
|
$protectedSessionTimeout.val(options['protectedSessionTimeout']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$form.submit(() => {
|
$form.submit(() => {
|
||||||
const protectedSessionTimeout = $protectedSessionTimeout.val();
|
const protectedSessionTimeout = $protectedSessionTimeout.val();
|
||||||
|
|
||||||
saveOptions(optionName, protectedSessionTimeout).then(() => {
|
saveOptions({ 'protectedSessionTimeout': protectedSessionTimeout }).then(() => {
|
||||||
protectedSessionHolder.setProtectedSessionTimeout(protectedSessionTimeout);
|
protectedSessionHolder.setProtectedSessionTimeout(protectedSessionTimeout);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -117,14 +152,13 @@ addTabHandler((function() {
|
|||||||
addTabHandler((function () {
|
addTabHandler((function () {
|
||||||
const $form = $("#note-revision-snapshot-time-interval-form");
|
const $form = $("#note-revision-snapshot-time-interval-form");
|
||||||
const $timeInterval = $("#note-revision-snapshot-time-interval-in-seconds");
|
const $timeInterval = $("#note-revision-snapshot-time-interval-in-seconds");
|
||||||
const optionName = 'noteRevisionSnapshotTimeInterval';
|
|
||||||
|
|
||||||
function optionsLoaded(options) {
|
function optionsLoaded(options) {
|
||||||
$timeInterval.val(options[optionName]);
|
$timeInterval.val(options['noteRevisionSnapshotTimeInterval']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$form.submit(() => {
|
$form.submit(() => {
|
||||||
saveOptions(optionName, $timeInterval.val());
|
saveOptions({ 'noteRevisionSnapshotTimeInterval': $timeInterval.val() });
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
@@ -137,6 +171,7 @@ addTabHandler((function () {
|
|||||||
addTabHandler((async function () {
|
addTabHandler((async function () {
|
||||||
const $appVersion = $("#app-version");
|
const $appVersion = $("#app-version");
|
||||||
const $dbVersion = $("#db-version");
|
const $dbVersion = $("#db-version");
|
||||||
|
const $syncVersion = $("#sync-version");
|
||||||
const $buildDate = $("#build-date");
|
const $buildDate = $("#build-date");
|
||||||
const $buildRevision = $("#build-revision");
|
const $buildRevision = $("#build-revision");
|
||||||
|
|
||||||
@@ -144,6 +179,7 @@ addTabHandler((async function () {
|
|||||||
|
|
||||||
$appVersion.html(appInfo.appVersion);
|
$appVersion.html(appInfo.appVersion);
|
||||||
$dbVersion.html(appInfo.dbVersion);
|
$dbVersion.html(appInfo.dbVersion);
|
||||||
|
$syncVersion.html(appInfo.syncVersion);
|
||||||
$buildDate.html(appInfo.buildDate);
|
$buildDate.html(appInfo.buildDate);
|
||||||
$buildRevision.html(appInfo.buildRevision);
|
$buildRevision.html(appInfo.buildRevision);
|
||||||
$buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision);
|
$buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision);
|
||||||
@@ -151,6 +187,57 @@ addTabHandler((async function () {
|
|||||||
return {};
|
return {};
|
||||||
})());
|
})());
|
||||||
|
|
||||||
|
addTabHandler((function() {
|
||||||
|
const $form = $("#sync-setup-form");
|
||||||
|
const $syncServerHost = $("#sync-server-host");
|
||||||
|
const $syncServerTimeout = $("#sync-server-timeout");
|
||||||
|
const $syncProxy = $("#sync-proxy");
|
||||||
|
const $testSyncButton = $("#test-sync-button");
|
||||||
|
const $syncToServerButton = $("#sync-to-server-button");
|
||||||
|
|
||||||
|
function optionsLoaded(options) {
|
||||||
|
$syncServerHost.val(options['syncServerHost']);
|
||||||
|
$syncServerTimeout.val(options['syncServerTimeout']);
|
||||||
|
$syncProxy.val(options['syncProxy']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$form.submit(() => {
|
||||||
|
saveOptions({
|
||||||
|
'syncServerHost': $syncServerHost.val(),
|
||||||
|
'syncServerTimeout': $syncServerTimeout.val(),
|
||||||
|
'syncProxy': $syncProxy.val()
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$testSyncButton.click(async () => {
|
||||||
|
const result = await server.post('sync/test');
|
||||||
|
|
||||||
|
if (result.connection === "Success") {
|
||||||
|
infoService.showMessage("Sync server handshake has been successful");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
infoService.showError("Sync server handshake failed, error: " + result.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$syncToServerButton.click(async () => {
|
||||||
|
const resp = await server.post("setup/sync-to-server");
|
||||||
|
|
||||||
|
if (resp.success) {
|
||||||
|
infoService.showMessage("Sync has been established to the server instance. It will take some time to finish.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
infoService.showError('Sync setup failed: ' + resp.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
optionsLoaded
|
||||||
|
};
|
||||||
|
})());
|
||||||
|
|
||||||
addTabHandler((async function () {
|
addTabHandler((async function () {
|
||||||
const $forceFullSyncButton = $("#force-full-sync-button");
|
const $forceFullSyncButton = $("#force-full-sync-button");
|
||||||
const $fillSyncRowsButton = $("#fill-sync-rows-button");
|
const $fillSyncRowsButton = $("#fill-sync-rows-button");
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ async function showDialog() {
|
|||||||
|
|
||||||
const result = await server.get('recent-changes/');
|
const result = await server.get('recent-changes/');
|
||||||
|
|
||||||
$dialog.html('');
|
$dialog.empty();
|
||||||
|
|
||||||
|
if (result.length === 0) {
|
||||||
|
$dialog.append("No changes yet ...");
|
||||||
|
}
|
||||||
|
|
||||||
const groupedByDate = groupByDate(result);
|
const groupedByDate = groupByDate(result);
|
||||||
|
|
||||||
@@ -30,9 +34,9 @@ async function showDialog() {
|
|||||||
const revLink = $("<a>", {
|
const revLink = $("<a>", {
|
||||||
href: 'javascript:',
|
href: 'javascript:',
|
||||||
text: 'rev'
|
text: 'rev'
|
||||||
}).attr('action', 'note-revision')
|
}).attr('data-action', 'note-revision')
|
||||||
.attr('note-path', change.noteId)
|
.attr('data-note-path', change.noteId)
|
||||||
.attr('note-revision-id', change.noteRevisionId);
|
.attr('data-note-revision-id', change.noteRevisionId);
|
||||||
|
|
||||||
let noteLink;
|
let noteLink;
|
||||||
|
|
||||||
@@ -40,7 +44,7 @@ async function showDialog() {
|
|||||||
noteLink = change.current_title;
|
noteLink = change.current_title;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
noteLink = linkService.createNoteLink(change.noteId, change.title);
|
noteLink = await linkService.createNoteLink(change.noteId, change.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
changesListEl.append($('<li>')
|
changesListEl.append($('<li>')
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
import treeService from '../services/tree.js';
|
|
||||||
import messagingService from '../services/messaging.js';
|
|
||||||
import server from '../services/server.js';
|
|
||||||
import utils from "../services/utils.js";
|
|
||||||
import treeUtils from "../services/tree_utils.js";
|
|
||||||
|
|
||||||
const $dialog = $("#recent-notes-dialog");
|
|
||||||
const $searchInput = $('#recent-notes-search-input');
|
|
||||||
|
|
||||||
// list of recent note paths
|
|
||||||
let list = [];
|
|
||||||
|
|
||||||
async function reload() {
|
|
||||||
const result = await server.get('recent-notes');
|
|
||||||
|
|
||||||
list = result.map(r => r.notePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addRecentNote(branchId, notePath) {
|
|
||||||
setTimeout(async () => {
|
|
||||||
// we include the note into recent list only if the user stayed on the note at least 5 seconds
|
|
||||||
if (notePath && notePath === treeService.getCurrentNotePath()) {
|
|
||||||
const result = await server.put('recent-notes/' + branchId + '/' + encodeURIComponent(notePath));
|
|
||||||
|
|
||||||
list = result.map(r => r.notePath);
|
|
||||||
}
|
|
||||||
}, 1500);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getNoteTitle(notePath) {
|
|
||||||
let noteTitle;
|
|
||||||
|
|
||||||
try {
|
|
||||||
noteTitle = await treeUtils.getNotePathTitle(notePath);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
noteTitle = "[error - can't find note title]";
|
|
||||||
|
|
||||||
messagingService.logError("Could not find title for notePath=" + notePath + ", stack=" + e.stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
return noteTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function showDialog() {
|
|
||||||
glob.activeDialog = $dialog;
|
|
||||||
|
|
||||||
$dialog.dialog({
|
|
||||||
modal: true,
|
|
||||||
width: 800,
|
|
||||||
height: 100,
|
|
||||||
position: { my: "center top+100", at: "top", of: window }
|
|
||||||
});
|
|
||||||
|
|
||||||
$searchInput.val('');
|
|
||||||
|
|
||||||
// remove the current note
|
|
||||||
const recNotes = list.filter(note => note !== treeService.getCurrentNotePath());
|
|
||||||
const items = [];
|
|
||||||
|
|
||||||
for (const notePath of recNotes) {
|
|
||||||
items.push({
|
|
||||||
label: await getNoteTitle(notePath),
|
|
||||||
value: notePath
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$searchInput.autocomplete({
|
|
||||||
source: items,
|
|
||||||
minLength: 0,
|
|
||||||
autoFocus: true,
|
|
||||||
select: function (event, ui) {
|
|
||||||
treeService.activateNode(ui.item.value);
|
|
||||||
|
|
||||||
$searchInput.autocomplete('destroy');
|
|
||||||
$dialog.dialog('close');
|
|
||||||
},
|
|
||||||
focus: function (event, ui) {
|
|
||||||
event.preventDefault();
|
|
||||||
},
|
|
||||||
close: function (event, ui) {
|
|
||||||
if (event.keyCode === 27) { // escape closes dialog
|
|
||||||
$searchInput.autocomplete('destroy');
|
|
||||||
$dialog.dialog('close');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// keep autocomplete open
|
|
||||||
// we're kind of abusing autocomplete to work in a way which it's not designed for
|
|
||||||
$searchInput.autocomplete("search", "");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
create: () => $searchInput.autocomplete("search", ""),
|
|
||||||
classes: {
|
|
||||||
"ui-autocomplete": "recent-notes-autocomplete"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(reload, 100);
|
|
||||||
|
|
||||||
messagingService.subscribeToMessages(syncData => {
|
|
||||||
if (syncData.some(sync => sync.entityName === 'recent_notes')) {
|
|
||||||
console.log(utils.now(), "Reloading recent notes because of background changes");
|
|
||||||
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
|
||||||
showDialog,
|
|
||||||
addRecentNote,
|
|
||||||
reload
|
|
||||||
};
|
|
||||||
@@ -1,19 +1,32 @@
|
|||||||
|
/** Represents mapping between note and parent note */
|
||||||
class Branch {
|
class Branch {
|
||||||
constructor(treeCache, row) {
|
constructor(treeCache, row) {
|
||||||
this.treeCache = treeCache;
|
this.treeCache = treeCache;
|
||||||
|
/** @param {string} primary key */
|
||||||
this.branchId = row.branchId;
|
this.branchId = row.branchId;
|
||||||
|
/** @param {string} */
|
||||||
this.noteId = row.noteId;
|
this.noteId = row.noteId;
|
||||||
this.note = null;
|
this.note = null;
|
||||||
|
/** @param {string} */
|
||||||
this.parentNoteId = row.parentNoteId;
|
this.parentNoteId = row.parentNoteId;
|
||||||
|
/** @param {int} */
|
||||||
this.notePosition = row.notePosition;
|
this.notePosition = row.notePosition;
|
||||||
|
/** @param {string} */
|
||||||
this.prefix = row.prefix;
|
this.prefix = row.prefix;
|
||||||
|
/** @param {boolean} */
|
||||||
this.isExpanded = row.isExpanded;
|
this.isExpanded = row.isExpanded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {NoteShort} */
|
||||||
async getNote() {
|
async getNote() {
|
||||||
return await this.treeCache.getNote(this.noteId);
|
return await this.treeCache.getNote(this.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {boolean} true if it's top level, meaning its parent is root note */
|
||||||
|
isTopLevel() {
|
||||||
|
return this.parentNoteId === 'root';
|
||||||
|
}
|
||||||
|
|
||||||
get toString() {
|
get toString() {
|
||||||
return `Branch(branchId=${this.branchId})`;
|
return `Branch(branchId=${this.branchId})`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
import NoteShort from './note_short.js';
|
import NoteShort from './note_short.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents full note, specifically including note's content.
|
||||||
|
*/
|
||||||
class NoteFull extends NoteShort {
|
class NoteFull extends NoteShort {
|
||||||
constructor(treeCache, row) {
|
constructor(treeCache, row) {
|
||||||
super(treeCache, row);
|
super(treeCache, row);
|
||||||
|
|
||||||
|
/** @param {string} */
|
||||||
this.content = row.content;
|
this.content = row.content;
|
||||||
|
|
||||||
if (this.isJson()) {
|
if (this.content !== "" && this.isJson()) {
|
||||||
this.jsonContent = JSON.parse(this.content);
|
try {
|
||||||
|
/** @param {object} */
|
||||||
|
this.jsonContent = JSON.parse(this.content);
|
||||||
|
}
|
||||||
|
catch(e) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* This note's representation is used in note tree and is kept in TreeCache.
|
||||||
|
* Its notable omission is the note content.
|
||||||
|
*/
|
||||||
class NoteShort {
|
class NoteShort {
|
||||||
constructor(treeCache, row) {
|
constructor(treeCache, row) {
|
||||||
this.treeCache = treeCache;
|
this.treeCache = treeCache;
|
||||||
|
/** @param {string} */
|
||||||
this.noteId = row.noteId;
|
this.noteId = row.noteId;
|
||||||
|
/** @param {string} */
|
||||||
this.title = row.title;
|
this.title = row.title;
|
||||||
|
/** @param {boolean} */
|
||||||
this.isProtected = row.isProtected;
|
this.isProtected = row.isProtected;
|
||||||
|
/** @param {string} one of 'text', 'code', 'file' or 'render' */
|
||||||
this.type = row.type;
|
this.type = row.type;
|
||||||
|
/** @param {string} content-type, e.g. "application/json" */
|
||||||
this.mime = row.mime;
|
this.mime = row.mime;
|
||||||
this.hideInAutocomplete = row.hideInAutocomplete;
|
/** @param {boolean} */
|
||||||
|
this.archived = row.archived;
|
||||||
|
this.cssClass = row.cssClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {boolean} */
|
||||||
isJson() {
|
isJson() {
|
||||||
return this.mime === "application/json";
|
return this.mime === "application/json";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Promise<Branch[]>} */
|
||||||
async getBranches() {
|
async getBranches() {
|
||||||
const branches = [];
|
const branchIds = this.treeCache.parents[this.noteId].map(
|
||||||
|
parentNoteId => this.treeCache.getBranchIdByChildParent(this.noteId, parentNoteId));
|
||||||
|
|
||||||
for (const parent of this.treeCache.parents[this.noteId]) {
|
return this.treeCache.getBranches(branchIds);
|
||||||
branches.push(await this.treeCache.getBranchByChildParent(this.noteId, parent.noteId));
|
|
||||||
}
|
|
||||||
|
|
||||||
return branches;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {boolean} */
|
||||||
|
hasChildren() {
|
||||||
|
return this.treeCache.children[this.noteId]
|
||||||
|
&& this.treeCache.children[this.noteId].length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {Promise<Branch[]>} */
|
||||||
async getChildBranches() {
|
async getChildBranches() {
|
||||||
const branches = [];
|
if (!this.treeCache.children[this.noteId]) {
|
||||||
|
return [];
|
||||||
for (const child of this.treeCache.children[this.noteId]) {
|
|
||||||
branches.push(await this.treeCache.getBranchByChildParent(child.noteId, this.noteId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return branches;
|
const branchIds = this.treeCache.children[this.noteId].map(
|
||||||
|
childNoteId => this.treeCache.getBranchIdByChildParent(childNoteId, this.noteId));
|
||||||
|
|
||||||
|
return await this.treeCache.getBranches(branchIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getParentNotes() {
|
/** @returns {string[]} */
|
||||||
|
getParentNoteIds() {
|
||||||
return this.treeCache.parents[this.noteId] || [];
|
return this.treeCache.parents[this.noteId] || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async getChildNotes() {
|
/** @returns {Promise<NoteShort[]>} */
|
||||||
|
async getParentNotes() {
|
||||||
|
return await this.treeCache.getNotes(this.getParentNoteIds());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {string[]} */
|
||||||
|
getChildNoteIds() {
|
||||||
return this.treeCache.children[this.noteId] || [];
|
return this.treeCache.children[this.noteId] || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Promise<NoteShort[]>} */
|
||||||
|
async getChildNotes() {
|
||||||
|
return await this.treeCache.getNotes(this.getChildNoteIds());
|
||||||
|
}
|
||||||
|
|
||||||
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.archived;
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NoteShort;
|
export default NoteShort;
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import server from './services/server.js';
|
|
||||||
|
|
||||||
$(document).ready(async () => {
|
|
||||||
const {appDbVersion, dbVersion} = await server.get('migration');
|
|
||||||
|
|
||||||
console.log("HI", {appDbVersion, dbVersion});
|
|
||||||
|
|
||||||
if (appDbVersion === dbVersion) {
|
|
||||||
$("#up-to-date").show();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$("#need-to-migrate").show();
|
|
||||||
|
|
||||||
$("#app-db-version").html(appDbVersion);
|
|
||||||
$("#db-version").html(dbVersion);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#run-migration").click(async () => {
|
|
||||||
$("#run-migration").prop("disabled", true);
|
|
||||||
|
|
||||||
$("#migration-result").show();
|
|
||||||
|
|
||||||
const result = await server.post('migration');
|
|
||||||
|
|
||||||
for (const migration of result.migrations) {
|
|
||||||
const row = $('<tr>')
|
|
||||||
.append($('<td>').html(migration.dbVersion))
|
|
||||||
.append($('<td>').html(migration.name))
|
|
||||||
.append($('<td>').html(migration.success ? 'Yes' : 'No'))
|
|
||||||
.append($('<td>').html(migration.success ? 'N/A' : migration.error));
|
|
||||||
|
|
||||||
if (!migration.success) {
|
|
||||||
row.addClass("danger");
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#migration-table").append(row);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// copy of this shortcut to be able to debug migration problems
|
|
||||||
$(document).bind('keydown', 'ctrl+shift+i', () => {
|
|
||||||
require('electron').remote.getCurrentWindow().toggleDevTools();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import treeCache from "./tree_cache.js";
|
|
||||||
import treeUtils from "./tree_utils.js";
|
|
||||||
|
|
||||||
async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
|
|
||||||
if (!parentNoteId) {
|
|
||||||
parentNoteId = 'root';
|
|
||||||
}
|
|
||||||
|
|
||||||
const parentNote = await treeCache.getNote(parentNoteId);
|
|
||||||
const childNotes = await parentNote.getChildNotes();
|
|
||||||
|
|
||||||
if (!childNotes.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!notePath) {
|
|
||||||
notePath = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!titlePath) {
|
|
||||||
titlePath = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/zadam/trilium/issues/46
|
|
||||||
// unfortunately not easy to implement because we don't have an easy access to note's isProtected property
|
|
||||||
|
|
||||||
const autocompleteItems = [];
|
|
||||||
|
|
||||||
for (const childNote of childNotes) {
|
|
||||||
if (childNote.hideInAutocomplete) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const childNotePath = (notePath ? (notePath + '/') : '') + childNote.noteId;
|
|
||||||
const childTitlePath = (titlePath ? (titlePath + ' / ') : '') + await treeUtils.getNoteTitle(childNote.noteId, parentNoteId);
|
|
||||||
|
|
||||||
autocompleteItems.push({
|
|
||||||
value: childTitlePath + ' (' + childNotePath + ')',
|
|
||||||
label: childTitlePath
|
|
||||||
});
|
|
||||||
|
|
||||||
const childItems = await getAutocompleteItems(childNote.noteId, childNotePath, childTitlePath);
|
|
||||||
|
|
||||||
for (const childItem of childItems) {
|
|
||||||
autocompleteItems.push(childItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parentNoteId === 'root') {
|
|
||||||
console.log(`Generated ${autocompleteItems.length} autocomplete items`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return autocompleteItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overrides the default autocomplete filter function to search for matched on atleast 1 word in each of the input term's words
|
|
||||||
$.ui.autocomplete.filter = (array, terms) => {
|
|
||||||
if (!terms) {
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
const startDate = new Date();
|
|
||||||
|
|
||||||
const results = [];
|
|
||||||
const tokens = terms.toLowerCase().split(" ");
|
|
||||||
|
|
||||||
for (const item of array) {
|
|
||||||
const lcLabel = item.label.toLowerCase();
|
|
||||||
|
|
||||||
const found = tokens.every(token => lcLabel.indexOf(token) !== -1);
|
|
||||||
if (!found) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is not completely correct and might cause minor problems with note with names containing this " / "
|
|
||||||
const lastSegmentIndex = lcLabel.lastIndexOf(" / ");
|
|
||||||
|
|
||||||
if (lastSegmentIndex !== -1) {
|
|
||||||
const lastSegment = lcLabel.substr(lastSegmentIndex + 3);
|
|
||||||
|
|
||||||
// at least some token needs to be in the last segment (leaf note), otherwise this
|
|
||||||
// particular note is not that interesting (query is satisfied by parent note)
|
|
||||||
const foundInLastSegment = tokens.some(token => lastSegment.indexOf(token) !== -1);
|
|
||||||
|
|
||||||
if (!foundInLastSegment) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
results.push(item);
|
|
||||||
|
|
||||||
if (results.length > 100) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Search took " + (new Date().getTime() - startDate.getTime()) + "ms");
|
|
||||||
|
|
||||||
return results;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getAutocompleteItems
|
|
||||||
};
|
|
||||||
27
src/public/javascripts/services/bootstrap.js
vendored
@@ -1,10 +1,9 @@
|
|||||||
import addLinkDialog from '../dialogs/add_link.js';
|
import addLinkDialog from '../dialogs/add_link.js';
|
||||||
import jumpToNoteDialog from '../dialogs/jump_to_note.js';
|
import jumpToNoteDialog from '../dialogs/jump_to_note.js';
|
||||||
import labelsDialog from '../dialogs/labels.js';
|
import attributesDialog from '../dialogs/attributes.js';
|
||||||
import noteRevisionsDialog from '../dialogs/note_revisions.js';
|
import noteRevisionsDialog from '../dialogs/note_revisions.js';
|
||||||
import noteSourceDialog from '../dialogs/note_source.js';
|
import noteSourceDialog from '../dialogs/note_source.js';
|
||||||
import recentChangesDialog from '../dialogs/recent_changes.js';
|
import recentChangesDialog from '../dialogs/recent_changes.js';
|
||||||
import recentNotesDialog from '../dialogs/recent_notes.js';
|
|
||||||
import optionsDialog from '../dialogs/options.js';
|
import optionsDialog from '../dialogs/options.js';
|
||||||
import sqlConsoleDialog from '../dialogs/sql_console.js';
|
import sqlConsoleDialog from '../dialogs/sql_console.js';
|
||||||
|
|
||||||
@@ -17,8 +16,8 @@ import messagingService from './messaging.js';
|
|||||||
import noteDetailService from './note_detail.js';
|
import noteDetailService from './note_detail.js';
|
||||||
import noteType from './note_type.js';
|
import noteType from './note_type.js';
|
||||||
import protected_session from './protected_session.js';
|
import protected_session from './protected_session.js';
|
||||||
import searchTreeService from './search_tree.js';
|
import searchNotesService from './search_notes.js';
|
||||||
import ScriptApi from './script_api.js';
|
import FrontendScriptApi from './frontend_script_api.js';
|
||||||
import ScriptContext from './script_context.js';
|
import ScriptContext from './script_context.js';
|
||||||
import sync from './sync.js';
|
import sync from './sync.js';
|
||||||
import treeService from './tree.js';
|
import treeService from './tree.js';
|
||||||
@@ -35,6 +34,9 @@ import libraryLoader from "./library_loader.js";
|
|||||||
// required for CKEditor image upload plugin
|
// required for CKEditor image upload plugin
|
||||||
window.glob.getCurrentNode = treeService.getCurrentNode;
|
window.glob.getCurrentNode = treeService.getCurrentNode;
|
||||||
window.glob.getHeaders = server.getHeaders;
|
window.glob.getHeaders = server.getHeaders;
|
||||||
|
window.glob.showAddLinkDialog = addLinkDialog.showDialog;
|
||||||
|
// this is required by CKEditor when uploading images
|
||||||
|
window.glob.noteChanged = noteDetailService.noteChanged;
|
||||||
|
|
||||||
// required for ESLint plugin
|
// required for ESLint plugin
|
||||||
window.glob.getCurrentNote = noteDetailService.getCurrentNote;
|
window.glob.getCurrentNote = noteDetailService.getCurrentNote;
|
||||||
@@ -46,7 +48,12 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
|
|||||||
|
|
||||||
let message = "Uncaught error: ";
|
let message = "Uncaught error: ";
|
||||||
|
|
||||||
if (string.indexOf("script error") > -1){
|
if (string.includes("Cannot read property 'defaultView' of undefined")) {
|
||||||
|
// ignore this specific error which is very common but we don't know where it comes from
|
||||||
|
// and it seems to be harmless
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (string.includes("script error")) {
|
||||||
message += 'No details available';
|
message += 'No details available';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -64,6 +71,14 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const wikiBaseUrl = "https://github.com/zadam/trilium/wiki/";
|
||||||
|
|
||||||
|
$(document).on("click", "button[data-help-page]", e => {
|
||||||
|
const $button = $(e.target);
|
||||||
|
|
||||||
|
window.open(wikiBaseUrl + $button.attr("data-help-page"), '_blank');
|
||||||
|
});
|
||||||
|
|
||||||
$("#logout-button").toggle(!utils.isElectron());
|
$("#logout-button").toggle(!utils.isElectron());
|
||||||
|
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
@@ -73,7 +88,7 @@ if (utils.isElectron()) {
|
|||||||
await treeService.reload();
|
await treeService.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
await treeService.activateNode(parentNoteId);
|
await treeService.activateNote(parentNoteId);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const node = treeService.getCurrentNode();
|
const node = treeService.getCurrentNode();
|
||||||
|
|||||||
@@ -1,12 +1,24 @@
|
|||||||
import ScriptContext from "./script_context.js";
|
import ScriptContext from "./script_context.js";
|
||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
|
import infoService from "./info.js";
|
||||||
|
|
||||||
async function executeBundle(bundle) {
|
async function getAndExecuteBundle(noteId, originEntity = null) {
|
||||||
const apiContext = ScriptContext(bundle.note, bundle.allNotes);
|
const bundle = await server.get('script/bundle/' + noteId);
|
||||||
|
|
||||||
return await (function () {
|
await executeBundle(bundle, originEntity);
|
||||||
return eval(`const apiContext = this; (async function() { ${bundle.script}\r\n})()`);
|
}
|
||||||
}.call(apiContext));
|
|
||||||
|
async function executeBundle(bundle, originEntity) {
|
||||||
|
const apiContext = ScriptContext(bundle.note, bundle.allNotes, originEntity);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await (function () {
|
||||||
|
return eval(`const apiContext = this; (async function() { ${bundle.script}\r\n})()`);
|
||||||
|
}.call(apiContext));
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
infoService.showAndLogError(`Execution of script "${bundle.note.title}" (${bundle.note.noteId}) failed with error: ${e.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executeStartupBundles() {
|
async function executeStartupBundles() {
|
||||||
@@ -17,7 +29,17 @@ async function executeStartupBundles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function executeRelationBundles(note, relationName) {
|
||||||
|
const bundlesToRun = await server.get("script/relation/" + note.noteId + "/" + relationName);
|
||||||
|
|
||||||
|
for (const bundle of bundlesToRun) {
|
||||||
|
await executeBundle(bundle, note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
executeBundle,
|
executeBundle,
|
||||||
executeStartupBundles
|
getAndExecuteBundle,
|
||||||
|
executeStartupBundles,
|
||||||
|
executeRelationBundles
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import treeUtils from './tree_utils.js';
|
|||||||
import branchPrefixDialog from '../dialogs/branch_prefix.js';
|
import branchPrefixDialog from '../dialogs/branch_prefix.js';
|
||||||
import infoService from "./info.js";
|
import infoService from "./info.js";
|
||||||
import treeCache from "./tree_cache.js";
|
import treeCache from "./tree_cache.js";
|
||||||
|
import syncService from "./sync.js";
|
||||||
|
|
||||||
const $tree = $("#tree");
|
const $tree = $("#tree");
|
||||||
|
|
||||||
@@ -93,27 +94,34 @@ const contextMenuOptions = {
|
|||||||
{title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"},
|
{title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"},
|
||||||
{title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"},
|
{title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"},
|
||||||
{title: "----"},
|
{title: "----"},
|
||||||
{title: "Export branch", cmd: "exportBranch", uiIcon: " ui-icon-arrowthick-1-ne"},
|
{title: "Export branch", cmd: "exportBranch", uiIcon: " ui-icon-arrowthick-1-ne", children: [
|
||||||
{title: "Import into branch", cmd: "importBranch", uiIcon: "ui-icon-arrowthick-1-sw"},
|
{title: "Native Tar", cmd: "exportBranchToTar"},
|
||||||
|
{title: "OPML", cmd: "exportBranchToOpml"}
|
||||||
|
]},
|
||||||
|
{title: "Import into branch (tar, opml)", cmd: "importBranch", uiIcon: "ui-icon-arrowthick-1-sw"},
|
||||||
{title: "----"},
|
{title: "----"},
|
||||||
{title: "Collapse branch <kbd>Alt+-</kbd>", cmd: "collapseBranch", uiIcon: "ui-icon-minus"},
|
{title: "Collapse branch <kbd>Alt+-</kbd>", cmd: "collapseBranch", uiIcon: "ui-icon-minus"},
|
||||||
{title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"},
|
{title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"},
|
||||||
{title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"}
|
{title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"}
|
||||||
|
|
||||||
],
|
],
|
||||||
beforeOpen: async (event, ui) => {
|
beforeOpen: async (event, ui) => {
|
||||||
const node = $.ui.fancytree.getNode(ui.target);
|
const node = $.ui.fancytree.getNode(ui.target);
|
||||||
const branch = await treeCache.getBranch(branchId);
|
const branch = await treeCache.getBranch(node.data.branchId);
|
||||||
const note = await treeCache.getNote(node.data.noteId);
|
const note = await treeCache.getNote(node.data.noteId);
|
||||||
const parentNote = await treeCache.getNote(branch.parentNoteId);
|
const parentNote = await treeCache.getNote(branch.parentNoteId);
|
||||||
|
const isNotRoot = note.noteId !== 'root';
|
||||||
|
|
||||||
// Modify menu entries depending on node status
|
// Modify menu entries depending on node status
|
||||||
$tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0 && (!parentNote || parentNote.type !== 'search'));
|
$tree.contextmenu("enableEntry", "insertNoteHere", isNotRoot && parentNote.type !== 'search');
|
||||||
$tree.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0 && note.type !== 'search');
|
|
||||||
$tree.contextmenu("enableEntry", "insertNoteHere", !parentNote || parentNote.type !== 'search');
|
|
||||||
$tree.contextmenu("enableEntry", "insertChildNote", note.type !== 'search');
|
$tree.contextmenu("enableEntry", "insertChildNote", note.type !== 'search');
|
||||||
|
$tree.contextmenu("enableEntry", "delete", isNotRoot && parentNote.type !== 'search');
|
||||||
|
$tree.contextmenu("enableEntry", "copy", isNotRoot);
|
||||||
|
$tree.contextmenu("enableEntry", "cut", isNotRoot);
|
||||||
|
$tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0 && isNotRoot && parentNote.type !== 'search');
|
||||||
|
$tree.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0 && note.type !== 'search');
|
||||||
$tree.contextmenu("enableEntry", "importBranch", note.type !== 'search');
|
$tree.contextmenu("enableEntry", "importBranch", note.type !== 'search');
|
||||||
$tree.contextmenu("enableEntry", "exportBranch", note.type !== 'search');
|
$tree.contextmenu("enableEntry", "exportBranch", note.type !== 'search');
|
||||||
|
$tree.contextmenu("enableEntry", "editBranchPrefix", isNotRoot && parentNote.type !== 'search');
|
||||||
|
|
||||||
// Activate node on right-click
|
// Activate node on right-click
|
||||||
node.setActive();
|
node.setActive();
|
||||||
@@ -158,8 +166,11 @@ const contextMenuOptions = {
|
|||||||
else if (ui.cmd === "delete") {
|
else if (ui.cmd === "delete") {
|
||||||
treeChangesService.deleteNodes(treeService.getSelectedNodes(true));
|
treeChangesService.deleteNodes(treeService.getSelectedNodes(true));
|
||||||
}
|
}
|
||||||
else if (ui.cmd === "exportBranch") {
|
else if (ui.cmd === "exportBranchToTar") {
|
||||||
exportService.exportBranch(node.data.noteId);
|
exportService.exportBranch(node.data.noteId, 'tar');
|
||||||
|
}
|
||||||
|
else if (ui.cmd === "exportBranchToOpml") {
|
||||||
|
exportService.exportBranch(node.data.noteId, 'opml');
|
||||||
}
|
}
|
||||||
else if (ui.cmd === "importBranch") {
|
else if (ui.cmd === "importBranch") {
|
||||||
exportService.importBranch(node.data.noteId);
|
exportService.importBranch(node.data.noteId);
|
||||||
|
|||||||