Compare commits

...

37 Commits

Author SHA1 Message Date
azivner
7a9542b4fc release 0.16.0 2018-06-16 13:34:39 -04:00
azivner
3a95c9e1bc all dialogs are now non-modal because of high cpu usage 2018-06-16 13:31:56 -04:00
azivner
3d2ef6be01 remove optionId, closes #117 2018-06-13 19:10:28 -04:00
azivner
d67246699a Introduced separate sync version (previously DB version was used to check sync compatibility), closes #120 2018-06-10 15:55:29 -04:00
azivner
14c704d6db db upgrades are now handled transparently in the background without bothering the user, closes #119 2018-06-10 15:49:22 -04:00
azivner
4c8eeb2e6f added docker build, closes #106 2018-06-10 15:06:52 -04:00
azivner
c1b245c8b1 fix unnecessary change events, closes #118 2018-06-10 11:51:13 -04:00
azivner
74202d67bb got rid of "Trilium Notes" branding - not necessary and takes valuable space 2018-06-10 10:57:45 -04:00
azivner
26066f39b1 chaged "focused mode" - now title is displayed as well and together with content takes whole window 2018-06-10 10:53:39 -04:00
azivner
b255cf190c fixes for zoom factor setting 2018-06-09 10:34:51 -04:00
azivner
bc77b143b0 darker outlines so inverted dark themes are more visible 2018-06-09 09:48:18 -04:00
azivner
9f0ff6ae7a note actions dropdown sizing 2018-06-09 09:44:40 -04:00
azivner
736704c7d6 fix show paths 2018-06-09 09:32:13 -04:00
azivner
654c116c58 use backgrounds for icon buttons so that dark and black themes look better 2018-06-09 09:28:50 -04:00
azivner
89a5cab98f added too options new tab appearance with possibility to change theme (white, black, dark) and zoom factor 2018-06-08 23:18:53 -04:00
azivner
c39d0be8cd refactoring of icon button styles 2018-06-08 22:17:00 -04:00
azivner
e75b4cd848 execute on script note is icon, closes #116 2018-06-08 21:59:40 -04:00
azivner
378e8f35e5 release 0.15.0 2018-06-07 23:09:21 -04:00
azivner
bdb5e2f13f no results also for "add link" 2018-06-07 23:08:41 -04:00
azivner
8211bed449 renamed icons according to their size, fixes #113 2018-06-07 23:02:21 -04:00
azivner
b243632483 usability improvements to autocomplete ("no results" etc.), needs refactoring 2018-06-07 20:18:46 -04:00
azivner
e4d2513451 close search button 2018-06-07 19:50:16 -04:00
azivner
385144451b renamed hideInAutocomplete label to archived 2018-06-07 19:26:28 -04:00
azivner
c8c533844e icons for render & toggle edit since text titles caused unwanted horizontal scrolling in smaller window sizes 2018-06-06 23:37:57 -04:00
azivner
0e69f0c079 fix recent notes issues 2018-06-06 22:38:36 -04:00
azivner
aee60c444f added "show results in full text" 2018-06-05 23:28:10 -04:00
azivner
e7a504c66b fixes and optimizations for search 2018-06-05 22:47:47 -04:00
azivner
45d9c7164c search refactorings 2018-06-05 19:12:52 -04:00
azivner
bd913a63a8 search note fixes 2018-06-04 23:21:45 -04:00
azivner
5a1938c078 better sizing of search pane 2018-06-04 20:22:41 -04:00
azivner
015cd68756 renaming/refactoring of search services 2018-06-04 19:48:02 -04:00
azivner
76c0e5b2b8 new UI for search, closes #108 (still needs cleanup) 2018-06-03 20:42:25 -04:00
azivner
0f8f707acd persisting zoom setting in electron, fixes #112 2018-06-02 13:02:20 -04:00
azivner
083cccea28 better protected/unprotected note indicator, fixes #110 2018-06-02 11:47:16 -04:00
azivner
31b76b23ce release 0.14.1 2018-06-02 09:39:37 -04:00
azivner
af529f82e5 fixed false sync error reporting 2018-06-02 09:39:04 -04:00
azivner
fc6669d254 initialization and schema fixes, closes #111 2018-06-01 22:26:37 -04:00
82 changed files with 895 additions and 627 deletions

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules
npm-debug.log
dist
.idea

View File

@@ -142,11 +142,10 @@ parentNoteId</ColNames>
<Primary>1</Primary> <Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName> <UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName>
</key> </key>
<column id="42" parent="8" name="id"> <column id="42" parent="8" name="eventId">
<Position>1</Position> <Position>1</Position>
<DataType>INTEGER|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column> </column>
<column id="43" parent="8" name="noteId"> <column id="43" parent="8" name="noteId">
<Position>2</Position> <Position>2</Position>
@@ -161,505 +160,517 @@ parentNoteId</ColNames>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<key id="46" parent="8"> <index id="46" parent="8" name="sqlite_autoindex_event_log_1">
<ColNames>id</ColNames> <NameSurrogate>1</NameSurrogate>
<ColNames>eventId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="47" parent="8">
<ColNames>eventId</ColNames>
<Primary>1</Primary> <Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_event_log_1</UnderlyingIndexName>
</key> </key>
<column id="47" parent="9" name="imageId"> <column id="48" parent="9" name="imageId">
<Position>1</Position> <Position>1</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="48" parent="9" name="format"> <column id="49" parent="9" name="format">
<Position>2</Position> <Position>2</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="49" parent="9" name="checksum"> <column id="50" parent="9" name="checksum">
<Position>3</Position> <Position>3</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="50" parent="9" name="name"> <column id="51" parent="9" name="name">
<Position>4</Position> <Position>4</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="51" parent="9" name="data"> <column id="52" parent="9" name="data">
<Position>5</Position> <Position>5</Position>
<DataType>BLOB|0s</DataType> <DataType>BLOB|0s</DataType>
</column> </column>
<column id="52" parent="9" name="isDeleted"> <column id="53" parent="9" name="isDeleted">
<Position>6</Position> <Position>6</Position>
<DataType>INT|0s</DataType> <DataType>INT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression> <DefaultExpression>0</DefaultExpression>
</column> </column>
<column id="53" parent="9" name="dateModified"> <column id="54" parent="9" name="dateModified">
<Position>7</Position> <Position>7</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="54" parent="9" name="dateCreated"> <column id="55" parent="9" name="dateCreated">
<Position>8</Position> <Position>8</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="55" parent="9" name="hash"> <column id="56" parent="9" name="hash">
<Position>9</Position> <Position>9</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression> <DefaultExpression>&quot;&quot;</DefaultExpression>
</column> </column>
<index id="56" parent="9" name="sqlite_autoindex_images_1"> <index id="57" parent="9" name="sqlite_autoindex_images_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>imageId</ColNames> <ColNames>imageId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<key id="57" parent="9"> <key id="58" parent="9">
<ColNames>imageId</ColNames> <ColNames>imageId</ColNames>
<Primary>1</Primary> <Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName> <UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName>
</key> </key>
<column id="58" parent="10" name="labelId"> <column id="59" parent="10" name="labelId">
<Position>1</Position> <Position>1</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="59" parent="10" name="noteId"> <column id="60" parent="10" name="noteId">
<Position>2</Position> <Position>2</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="60" parent="10" name="name"> <column id="61" parent="10" name="name">
<Position>3</Position> <Position>3</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="61" parent="10" name="value"> <column id="62" parent="10" name="value">
<Position>4</Position> <Position>4</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression> <DefaultExpression>&apos;&apos;</DefaultExpression>
</column> </column>
<column id="62" parent="10" name="position"> <column id="63" parent="10" name="position">
<Position>5</Position> <Position>5</Position>
<DataType>INT|0s</DataType> <DataType>INT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression> <DefaultExpression>0</DefaultExpression>
</column> </column>
<column id="63" parent="10" name="dateCreated"> <column id="64" parent="10" name="dateCreated">
<Position>6</Position> <Position>6</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="64" parent="10" name="dateModified"> <column id="65" 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="65" parent="10" name="isDeleted"> <column id="66" parent="10" name="isDeleted">
<Position>8</Position> <Position>8</Position>
<DataType>INT|0s</DataType> <DataType>INT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="66" parent="10" name="hash"> <column id="67" parent="10" name="hash">
<Position>9</Position> <Position>9</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression> <DefaultExpression>&quot;&quot;</DefaultExpression>
</column> </column>
<index id="67" parent="10" name="sqlite_autoindex_labels_1"> <index id="68" parent="10" name="sqlite_autoindex_labels_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>labelId</ColNames> <ColNames>labelId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<index id="68" parent="10" name="IDX_labels_noteId"> <index id="69" parent="10" name="IDX_labels_noteId">
<ColNames>noteId</ColNames> <ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<index id="69" parent="10" name="IDX_labels_name_value"> <index id="70" parent="10" name="IDX_labels_name_value">
<ColNames>name <ColNames>name
value</ColNames> value</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<key id="70" parent="10"> <key id="71" parent="10">
<ColNames>labelId</ColNames> <ColNames>labelId</ColNames>
<Primary>1</Primary> <Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName> <UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName>
</key> </key>
<column id="71" parent="11" name="noteImageId"> <column id="72" parent="11" name="noteImageId">
<Position>1</Position> <Position>1</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="72" parent="11" name="noteId"> <column id="73" parent="11" name="noteId">
<Position>2</Position> <Position>2</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="73" parent="11" name="imageId"> <column id="74" parent="11" name="imageId">
<Position>3</Position> <Position>3</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="74" parent="11" name="isDeleted"> <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="75" 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="76" 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>
<column id="77" parent="11" name="hash"> <column id="78" parent="11" name="hash">
<Position>7</Position> <Position>7</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression> <DefaultExpression>&quot;&quot;</DefaultExpression>
</column> </column>
<index id="78" parent="11" name="sqlite_autoindex_note_images_1"> <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> <ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<index id="79" 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> <ColumnCollations></ColumnCollations>
</index> </index>
<index id="80" 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> <ColumnCollations></ColumnCollations>
</index> </index>
<index id="81" 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> <ColumnCollations></ColumnCollations>
</index> </index>
<key id="82" 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="83" 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="84" 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="85" 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="86" 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="87" 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="88" 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="89" 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>
<column id="90" parent="12" name="type"> <column id="91" parent="12" name="type">
<Position>8</Position> <Position>8</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression> <DefaultExpression>&apos;&apos;</DefaultExpression>
</column> </column>
<column id="91" parent="12" name="mime"> <column id="92" parent="12" name="mime">
<Position>9</Position> <Position>9</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression> <DefaultExpression>&apos;&apos;</DefaultExpression>
</column> </column>
<column id="92" parent="12" name="hash"> <column id="93" parent="12" name="hash">
<Position>10</Position> <Position>10</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression> <DefaultExpression>&quot;&quot;</DefaultExpression>
</column> </column>
<index id="93" parent="12" name="sqlite_autoindex_note_revisions_1"> <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> <ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<index id="94" 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> <ColumnCollations></ColumnCollations>
</index> </index>
<index id="95" 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> <ColumnCollations></ColumnCollations>
</index> </index>
<index id="96" 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> <ColumnCollations></ColumnCollations>
</index> </index>
<key id="97" 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="98" 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="99" 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> <NotNull>1</NotNull>
<DefaultExpression>&quot;unnamed&quot;</DefaultExpression> <DefaultExpression>&quot;unnamed&quot;</DefaultExpression>
</column> </column>
<column id="100" 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> <NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression> <DefaultExpression>&quot;&quot;</DefaultExpression>
</column> </column>
<column id="101" 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="102" 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="103" 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="104" 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="105" 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>&apos;text&apos;</DefaultExpression> <DefaultExpression>&apos;text&apos;</DefaultExpression>
</column> </column>
<column id="106" 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>&apos;text/html&apos;</DefaultExpression> <DefaultExpression>&apos;text/html&apos;</DefaultExpression>
</column> </column>
<column id="107" parent="13" name="hash"> <column id="108" parent="13" name="hash">
<Position>10</Position> <Position>10</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression> <DefaultExpression>&quot;&quot;</DefaultExpression>
</column> </column>
<index id="108" parent="13" name="sqlite_autoindex_notes_1"> <index id="109" parent="13" name="sqlite_autoindex_notes_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>noteId</ColNames> <ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<index id="109" parent="13" name="IDX_notes_type"> <index id="110" parent="13" name="IDX_notes_type">
<ColNames>type</ColNames> <ColNames>type</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<key id="110" 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="111" parent="14" name="name"> <column id="112" parent="14" name="optionId">
<Position>1</Position> <Position>1</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="112" parent="14" name="value"> <column id="113" parent="14" name="name">
<Position>2</Position> <Position>2</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column> </column>
<column id="113" parent="14" name="dateModified"> <column id="114" parent="14" name="value">
<Position>3</Position> <Position>3</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="115" parent="14" name="dateModified">
<Position>4</Position>
<DataType>INT|0s</DataType> <DataType>INT|0s</DataType>
</column> </column>
<column id="114" parent="14" name="isSynced"> <column id="116" parent="14" name="isSynced">
<Position>4</Position> <Position>5</Position>
<DataType>INTEGER|0s</DataType> <DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression> <DefaultExpression>0</DefaultExpression>
</column> </column>
<column id="115" parent="14" name="hash"> <column id="117" parent="14" name="hash">
<Position>5</Position> <Position>6</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression> <DefaultExpression>&quot;&quot;</DefaultExpression>
</column> </column>
<column id="116" parent="14" name="dateCreated"> <column id="118" parent="14" name="dateCreated">
<Position>6</Position> <Position>7</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>&apos;1970-01-01T00:00:00.000Z&apos;</DefaultExpression> <DefaultExpression>&apos;1970-01-01T00:00:00.000Z&apos;</DefaultExpression>
</column> </column>
<index id="117" parent="14" name="sqlite_autoindex_options_1"> <index id="119" parent="14" name="sqlite_autoindex_options_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>name</ColNames> <ColNames>optionId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<key id="118" parent="14"> <key id="120" parent="14">
<ColNames>name</ColNames> <ColNames>optionId</ColNames>
<Primary>1</Primary> <Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName> <UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName>
</key> </key>
<column id="119" parent="15" name="branchId"> <column id="121" 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="120" parent="15" name="notePath"> <column id="122" 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="121" parent="15" name="dateCreated">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="122" parent="15" name="isDeleted">
<Position>4</Position>
<DataType>INT|0s</DataType>
</column>
<column id="123" parent="15" name="hash"> <column id="123" parent="15" name="hash">
<Position>5</Position> <Position>3</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression> <DefaultExpression>&quot;&quot;</DefaultExpression>
</column> </column>
<index id="124" parent="15" name="sqlite_autoindex_recent_notes_1"> <column id="124" parent="15" name="dateCreated">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="125" parent="15" name="isDeleted">
<Position>5</Position>
<DataType>INT|0s</DataType>
</column>
<index id="126" parent="15" name="sqlite_autoindex_recent_notes_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>branchId</ColNames> <ColNames>branchId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<key id="125" parent="15"> <key id="127" 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="126" parent="16" name="sourceId"> <column id="128" 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="127" parent="16" name="dateCreated"> <column id="129" 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="128" parent="16" name="sqlite_autoindex_source_ids_1"> <index id="130" parent="16" name="sqlite_autoindex_source_ids_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>sourceId</ColNames> <ColNames>sourceId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<key id="129" parent="16"> <key id="131" 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="130" parent="17" name="type"> <column id="132" parent="17" name="type">
<Position>1</Position> <Position>1</Position>
<DataType>text|0s</DataType> <DataType>text|0s</DataType>
</column> </column>
<column id="131" parent="17" name="name"> <column id="133" parent="17" name="name">
<Position>2</Position> <Position>2</Position>
<DataType>text|0s</DataType> <DataType>text|0s</DataType>
</column> </column>
<column id="132" parent="17" name="tbl_name"> <column id="134" parent="17" name="tbl_name">
<Position>3</Position> <Position>3</Position>
<DataType>text|0s</DataType> <DataType>text|0s</DataType>
</column> </column>
<column id="133" parent="17" name="rootpage"> <column id="135" parent="17" name="rootpage">
<Position>4</Position> <Position>4</Position>
<DataType>integer|0s</DataType> <DataType>integer|0s</DataType>
</column> </column>
<column id="134" parent="17" name="sql"> <column id="136" parent="17" name="sql">
<Position>5</Position> <Position>5</Position>
<DataType>text|0s</DataType> <DataType>text|0s</DataType>
</column> </column>
<column id="135" parent="18" name="name"> <column id="137" parent="18" name="name">
<Position>1</Position> <Position>1</Position>
</column> </column>
<column id="136" parent="18" name="seq"> <column id="138" parent="18" name="seq">
<Position>2</Position> <Position>2</Position>
</column> </column>
<column id="137" parent="19" name="id"> <column id="139" 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="138" parent="19" name="entityName"> <column id="140" 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="139" parent="19" name="entityId"> <column id="141" 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="140" parent="19" name="sourceId"> <column id="142" 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="141" parent="19" name="syncDate"> <column id="143" 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="142" parent="19" name="IDX_sync_entityName_entityId"> <index id="144" parent="19" name="IDX_sync_entityName_entityId">
<ColNames>entityName <ColNames>entityName
entityId</ColNames> entityId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<index id="143" parent="19" name="IDX_sync_syncDate"> <index id="145" parent="19" name="IDX_sync_syncDate">
<ColNames>syncDate</ColNames> <ColNames>syncDate</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<key id="144" parent="19"> <key id="146" parent="19">
<ColNames>id</ColNames> <ColNames>id</ColNames>
<Primary>1</Primary> <Primary>1</Primary>
</key> </key>

21
Dockerfile Normal file
View 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
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
if [[ $# -eq 0 ]] ; then
echo "Missing argument of new version"
exit 1
fi
docker build -t zadam/trilium:latest -t zadam/trilium:$1 .

9
bin/push-docker-image.sh Normal file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
if [[ $# -eq 0 ]] ; then
echo "Missing argument of new version"
exit 1
fi
docker push zadam/trilium:latest
docker push zadam/trilium:$1

View File

@@ -75,4 +75,8 @@ github-release upload \
--name "$WINDOWS_X64_BUILD" \ --name "$WINDOWS_X64_BUILD" \
--file "dist/$WINDOWS_X64_BUILD" --file "dist/$WINDOWS_X64_BUILD"
bin/build-docker.sh $VERSION
bin/push-docker-image.sh $VERSION
echo "Release finished!" echo "Release finished!"

View 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);

View File

@@ -0,0 +1 @@
UPDATE labels SET name = 'archived' WHERE name = 'hideInAutocomplete'

View 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);

View 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;

View File

@@ -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,
@@ -29,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); , 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`
); );
@@ -49,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,
@@ -58,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);
@@ -68,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,
@@ -77,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` (
@@ -87,12 +82,6 @@ CREATE INDEX `IDX_branches_noteId_parentNoteId` ON `branches` (
`noteId`, `noteId`,
`parentNoteId` `parentNoteId`
); );
CREATE TABLE IF NOT EXISTS "recent_notes" (
`branchId` TEXT NOT NULL PRIMARY KEY,
`notePath` TEXT NOT NULL,
`dateAccessed` TEXT NOT NULL,
isDeleted INT
);
CREATE TABLE labels CREATE TABLE labels
( (
labelId TEXT not null primary key, labelId TEXT not null primary key,
@@ -103,18 +92,11 @@ CREATE TABLE labels
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);
CREATE INDEX IDX_labels_name_value CREATE INDEX IDX_labels_name_value
on labels (name, value); on labels (name, value);
CREATE INDEX IDX_labels_noteId CREATE INDEX IDX_labels_noteId
on labels (noteId); on labels (noteId);
CREATE TABLE IF NOT EXISTS "event_log"
(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
noteId TEXT,
comment TEXT,
dateAdded TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS "notes" ( CREATE TABLE IF NOT EXISTS "notes" (
`noteId` TEXT NOT NULL, `noteId` TEXT NOT NULL,
`title` TEXT NOT NULL DEFAULT "unnamed", `title` TEXT NOT NULL DEFAULT "unnamed",
@@ -124,9 +106,31 @@ CREATE TABLE IF NOT EXISTS "notes" (
`dateCreated` TEXT NOT NULL, `dateCreated` TEXT NOT NULL,
`dateModified` TEXT NOT NULL, `dateModified` TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'text', type TEXT NOT NULL DEFAULT 'text',
mime TEXT NOT NULL DEFAULT 'text/html', mime TEXT NOT NULL DEFAULT 'text/html', hash TEXT DEFAULT "" NOT NULL,
PRIMARY KEY(`noteId`) PRIMARY KEY(`noteId`)
); );
CREATE INDEX `IDX_notes_isDeleted` ON `notes` ( CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId);
`isDeleted` CREATE INDEX IDX_notes_type
on notes (type);
CREATE TABLE IF NOT EXISTS "recent_notes" (
`branchId` TEXT NOT NULL PRIMARY KEY,
`notePath` TEXT NOT NULL,
hash TEXT DEFAULT "" NOT NULL,
`dateCreated` TEXT NOT NULL,
isDeleted INT
);
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"
(
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
); );

View File

@@ -1,7 +1,7 @@
{ {
"name": "trilium", "name": "trilium",
"description": "Trilium Notes", "description": "Trilium Notes",
"version": "0.14.0", "version": "0.16.0",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"main": "electron.js", "main": "electron.js",
"repository": { "repository": {

View File

@@ -21,11 +21,8 @@ class Entity {
contentToHash += "|" + this[propertyName]; contentToHash += "|" + this[propertyName];
} }
// this IF is to ease the migration from before hashed options, can be later removed
if (this.constructor.tableName !== 'options' || this.isSynced) {
this["hash"] = utils.hash(contentToHash).substr(0, 10); this["hash"] = utils.hash(contentToHash).substr(0, 10);
} }
}
async save() { async save() {
await repository.updateEntity(this); await repository.updateEntity(this);

View File

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 511 B

View File

Before

Width:  |  Height:  |  Size: 245 B

After

Width:  |  Height:  |  Size: 245 B

View File

Before

Width:  |  Height:  |  Size: 339 B

After

Width:  |  Height:  |  Size: 339 B

View File

Before

Width:  |  Height:  |  Size: 463 B

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

View File

Before

Width:  |  Height:  |  Size: 288 B

After

Width:  |  Height:  |  Size: 288 B

View File

Before

Width:  |  Height:  |  Size: 284 B

After

Width:  |  Height:  |  Size: 284 B

View File

Before

Width:  |  Height:  |  Size: 292 B

After

Width:  |  Height:  |  Size: 292 B

View File

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 511 B

View File

Before

Width:  |  Height:  |  Size: 155 B

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 B

View File

Before

Width:  |  Height:  |  Size: 358 B

After

Width:  |  Height:  |  Size: 358 B

View File

Before

Width:  |  Height:  |  Size: 252 B

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

View File

Before

Width:  |  Height:  |  Size: 419 B

After

Width:  |  Height:  |  Size: 419 B

View File

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

View File

Before

Width:  |  Height:  |  Size: 240 B

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

View File

@@ -57,7 +57,15 @@ async function showDialog() {
source: async function(request, response) { source: async function(request, response) {
const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term)); const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term));
if (result.length > 0) {
response(result); response(result);
}
else {
response([{
label: "No results",
value: "No results"
}]);
}
}, },
minLength: 2, minLength: 2,
change: async () => { change: async () => {

View File

@@ -1,10 +1,13 @@
import treeService from '../services/tree.js'; import treeService from '../services/tree.js';
import linkService from '../services/link.js'; import linkService from '../services/link.js';
import server from '../services/server.js'; import server from '../services/server.js';
import searchNotesService from '../services/search_notes.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 $form = $("#jump-to-note-form");
const $jumpToNoteButton = $("#jump-to-note-button");
const $showInFullTextButton = $("#show-in-full-text-button");
async function showDialog() { async function showDialog() {
glob.activeDialog = $dialog; glob.activeDialog = $dialog;
@@ -20,7 +23,18 @@ async function showDialog() {
source: async function(request, response) { source: async function(request, response) {
const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term)); const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term));
if (result.length > 0) {
response(result); response(result);
}
else {
response([{
label: "No results",
value: "No results"
}]);
}
},
focus: function(event, ui) {
return $(ui.item).val() !== 'No results';
}, },
minLength: 2 minLength: 2
}); });
@@ -41,12 +55,32 @@ function goToNote() {
} }
} }
function showInFullText(e) {
// stop from propagating upwards (dangerous especially with ctrl+enter executable javascript notes)
e.preventDefault();
e.stopPropagation();
const searchText = $autoComplete.val();
searchNotesService.resetSearch();
searchNotesService.showSearch();
searchNotesService.doSearch(searchText);
$dialog.dialog('close');
}
$form.submit(() => { $form.submit(() => {
goToNote(); goToNote();
return false; return false;
}); });
$jumpToNoteButton.click(goToNote);
$showInFullTextButton.click(showInFullText);
$dialog.bind('keydown', 'ctrl+return', showInFullText);
export default { export default {
showDialog showDialog
}; };

View File

@@ -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");
@@ -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");
@@ -137,6 +173,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 +181,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);

View File

@@ -1,47 +1,18 @@
import treeService from '../services/tree.js'; import treeService from '../services/tree.js';
import messagingService from '../services/messaging.js';
import server from '../services/server.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 $dialog = $("#recent-notes-dialog");
const $searchInput = $('#recent-notes-search-input'); 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) { function addRecentNote(branchId, notePath) {
setTimeout(async () => { setTimeout(async () => {
// we include the note into recent list only if the user stayed on the note at least 5 seconds // we include the note into recent list only if the user stayed on the note at least 5 seconds
if (notePath && notePath === treeService.getCurrentNotePath()) { if (notePath && notePath === treeService.getCurrentNotePath()) {
const result = await server.put('recent-notes/' + branchId + '/' + encodeURIComponent(notePath)); const result = await server.put('recent-notes/' + branchId + '/' + encodeURIComponent(notePath));
list = result.map(r => r.notePath);
} }
}, 1500); }, 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() { async function showDialog() {
glob.activeDialog = $dialog; glob.activeDialog = $dialog;
@@ -54,16 +25,17 @@ async function showDialog() {
$searchInput.val(''); $searchInput.val('');
// remove the current note const result = await server.get('recent-notes');
const recNotes = list.filter(note => note !== treeService.getCurrentNotePath());
const items = [];
for (const notePath of recNotes) { // remove the current note
items.push({ const recNotes = result.filter(note => note.notePath !== treeService.getCurrentNotePath());
label: await getNoteTitle(notePath),
value: notePath const items = recNotes.map(rn => {
return {
label: rn.title,
value: rn.notePath
};
}); });
}
$searchInput.autocomplete({ $searchInput.autocomplete({
source: items, source: items,
@@ -96,18 +68,7 @@ async function showDialog() {
}); });
} }
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 { export default {
showDialog, showDialog,
addRecentNote, addRecentNote
reload
}; };

View File

@@ -6,7 +6,7 @@ class NoteShort {
this.isProtected = row.isProtected; this.isProtected = row.isProtected;
this.type = row.type; this.type = row.type;
this.mime = row.mime; this.mime = row.mime;
this.hideInAutocomplete = row.hideInAutocomplete; this.archived = row.archived;
} }
isJson() { isJson() {
@@ -59,7 +59,7 @@ class NoteShort {
get dto() { get dto() {
const dto = Object.assign({}, this); const dto = Object.assign({}, this);
delete dto.treeCache; delete dto.treeCache;
delete dto.hideInAutocomplete; delete dto.archived;
return dto; return dto;
} }

View File

@@ -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;
});

View File

@@ -17,7 +17,7 @@ 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 ScriptApi from './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';

View File

@@ -114,13 +114,14 @@ const contextMenuOptions = {
// Modify menu entries depending on node status // Modify menu entries depending on node status
$tree.contextmenu("enableEntry", "insertNoteHere", isNotRoot && parentNote.type !== 'search'); $tree.contextmenu("enableEntry", "insertNoteHere", isNotRoot && parentNote.type !== 'search');
$tree.contextmenu("enableEntry", "insertChildNote", note.type !== 'search'); $tree.contextmenu("enableEntry", "insertChildNote", note.type !== 'search');
$tree.contextmenu("enableEntry", "delete", isNotRoot); $tree.contextmenu("enableEntry", "delete", isNotRoot && parentNote.type !== 'search');
$tree.contextmenu("enableEntry", "copy", isNotRoot); $tree.contextmenu("enableEntry", "copy", isNotRoot);
$tree.contextmenu("enableEntry", "cut", isNotRoot); $tree.contextmenu("enableEntry", "cut", isNotRoot);
$tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0 && isNotRoot && parentNote.type !== 'search'); $tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0 && isNotRoot && parentNote.type !== 'search');
$tree.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0 && note.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", parentNote.type !== 'search');
// Activate node on right-click // Activate node on right-click
node.setActive(); node.setActive();

View File

@@ -2,6 +2,7 @@ import utils from "./utils.js";
import treeService from "./tree.js"; import treeService from "./tree.js";
import linkService from "./link.js"; import linkService from "./link.js";
import fileService from "./file.js"; import fileService from "./file.js";
import zoomService from "./zoom.js";
import noteRevisionsDialog from "../dialogs/note_revisions.js"; import noteRevisionsDialog from "../dialogs/note_revisions.js";
import optionsDialog from "../dialogs/options.js"; import optionsDialog from "../dialogs/options.js";
import addLinkDialog from "../dialogs/add_link.js"; import addLinkDialog from "../dialogs/add_link.js";
@@ -10,7 +11,7 @@ import jumpToNoteDialog from "../dialogs/jump_to_note.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 sqlConsoleDialog from "../dialogs/sql_console.js"; import sqlConsoleDialog from "../dialogs/sql_console.js";
import searchTreeService from "./search_tree.js"; import searchNotesService from "./search_notes.js";
import labelsDialog from "../dialogs/labels.js"; import labelsDialog from "../dialogs/labels.js";
import protectedSessionService from "./protected_session.js"; import protectedSessionService from "./protected_session.js";
@@ -22,7 +23,7 @@ function registerEntrypoints() {
utils.bindShortcut('ctrl+l', addLinkDialog.showDialog); utils.bindShortcut('ctrl+l', addLinkDialog.showDialog);
$("#jump-to-note-button").click(jumpToNoteDialog.showDialog); $("#jump-to-note-dialog-button").click(jumpToNoteDialog.showDialog);
utils.bindShortcut('ctrl+j', jumpToNoteDialog.showDialog); utils.bindShortcut('ctrl+j', jumpToNoteDialog.showDialog);
$("#show-note-revisions-button").click(noteRevisionsDialog.showCurrentNoteRevisions); $("#show-note-revisions-button").click(noteRevisionsDialog.showCurrentNoteRevisions);
@@ -38,8 +39,8 @@ function registerEntrypoints() {
$("#recent-notes-button").click(recentNotesDialog.showDialog); $("#recent-notes-button").click(recentNotesDialog.showDialog);
utils.bindShortcut('ctrl+e', recentNotesDialog.showDialog); utils.bindShortcut('ctrl+e', recentNotesDialog.showDialog);
$("#toggle-search-button").click(searchTreeService.toggleSearch); $("#toggle-search-button").click(searchNotesService.toggleSearch);
utils.bindShortcut('ctrl+s', searchTreeService.toggleSearch); utils.bindShortcut('ctrl+s', searchNotesService.toggleSearch);
$(".show-labels-button").click(labelsDialog.showDialog); $(".show-labels-button").click(labelsDialog.showDialog);
utils.bindShortcut('alt+l', labelsDialog.showDialog); utils.bindShortcut('alt+l', labelsDialog.showDialog);
@@ -57,7 +58,13 @@ function registerEntrypoints() {
utils.bindShortcut('alt+right', window.history.forward); utils.bindShortcut('alt+right', window.history.forward);
} }
utils.bindShortcut('alt+m', e => $(".hide-toggle").toggleClass("suppressed")); utils.bindShortcut('alt+m', e => {
$(".hide-toggle").toggle();
// when hiding switch display to block, otherwise grid still tries to display columns which shows
// left empty column
$("#container").css("display", $("#container").css("display") === "grid" ? "block" : "grid");
});
// hide (toggle) everything except for the note content for distraction free writing // hide (toggle) everything except for the note content for distraction free writing
utils.bindShortcut('alt+t', e => { utils.bindShortcut('alt+t', e => {
@@ -109,28 +116,11 @@ function registerEntrypoints() {
$("#note-detail-text").focus(); $("#note-detail-text").focus();
}); });
$(document).bind('keydown', 'ctrl+-', () => {
if (utils.isElectron()) { if (utils.isElectron()) {
const webFrame = require('electron').webFrame; $(document).bind('keydown', 'ctrl+-', zoomService.decreaseZoomFactor);
$(document).bind('keydown', 'ctrl+=', zoomService.increaseZoomFactor);
if (webFrame.getZoomFactor() > 0.2) {
webFrame.setZoomFactor(webFrame.getZoomFactor() - 0.1);
} }
return false;
}
});
$(document).bind('keydown', 'ctrl+=', () => {
if (utils.isElectron()) {
const webFrame = require('electron').webFrame;
webFrame.setZoomFactor(webFrame.getZoomFactor() + 0.1);
return false;
}
});
$("#note-title").bind('keydown', 'return', () => $("#note-detail-text").focus()); $("#note-title").bind('keydown', 'return', () => $("#note-detail-text").focus());
$("#upload-file-button").click(fileService.uploadFile); $("#upload-file-button").click(fileService.uploadFile);

View File

@@ -22,6 +22,7 @@ const $noteDetailComponents = $(".note-detail-component");
const $protectButton = $("#protect-button"); const $protectButton = $("#protect-button");
const $unprotectButton = $("#unprotect-button"); const $unprotectButton = $("#unprotect-button");
const $noteDetailWrapper = $("#note-detail-wrapper"); const $noteDetailWrapper = $("#note-detail-wrapper");
const $noteDetailComponentWrapper = $("#note-detail-component-wrapper");
const $noteIdDisplay = $("#note-id-display"); const $noteIdDisplay = $("#note-id-display");
const $labelList = $("#label-list"); const $labelList = $("#label-list");
const $labelListInner = $("#label-list-inner"); const $labelListInner = $("#label-list-inner");
@@ -116,9 +117,9 @@ async function saveNoteIfChanged() {
function setNoteBackgroundIfProtected(note) { function setNoteBackgroundIfProtected(note) {
const isProtected = !!note.isProtected; const isProtected = !!note.isProtected;
$noteDetailWrapper.toggleClass("protected", isProtected); $noteDetailComponentWrapper.toggleClass("protected", isProtected);
$protectButton.toggle(!isProtected); $protectButton.toggleClass("active", isProtected);
$unprotectButton.toggle(isProtected); $unprotectButton.toggleClass("active", !isProtected);
} }
let isNewNoteCreated = false; let isNewNoteCreated = false;
@@ -150,6 +151,8 @@ async function loadNoteDetail(noteId) {
$noteIdDisplay.html(noteId); $noteIdDisplay.html(noteId);
setNoteBackgroundIfProtected(currentNote);
await handleProtectedSession(); await handleProtectedSession();
$noteDetailWrapper.show(); $noteDetailWrapper.show();
@@ -170,7 +173,6 @@ async function loadNoteDetail(noteId) {
noteChangeDisabled = false; noteChangeDisabled = false;
} }
setNoteBackgroundIfProtected(currentNote);
treeService.setBranchBackgroundBasedOnProtectedStatus(noteId); treeService.setBranchBackgroundBasedOnProtectedStatus(noteId);
// after loading new note make sure editor is scrolled to the top // after loading new note make sure editor is scrolled to the top

View File

@@ -11,7 +11,13 @@ async function show() {
textEditor = await BalloonEditor.create($noteDetailText[0], {}); textEditor = await BalloonEditor.create($noteDetailText[0], {});
textEditor.model.document.on('change', noteDetailService.noteChanged); textEditor.model.document.on('change', () => {
// change is triggered on just marker/selection changes which is not interesting for us
if (textEditor.model.document.differ.getChanges().length > 0) {
noteDetailService.noteChanged();
}
}
);
} }
textEditor.setData(noteDetailService.getCurrentNote().content); textEditor.setData(noteDetailService.getCurrentNote().content);

View File

@@ -0,0 +1,9 @@
import server from "./server.js";
const optionsReady = new Promise((resolve, reject) => {
$(document).ready(() => server.get('options').then(resolve));
});
export default {
optionsReady
}

View File

@@ -39,7 +39,10 @@ function ensureProtectedSession(requireProtectedSession, modal) {
} }
$dialog.dialog({ $dialog.dialog({
modal: modal, // modal: modal,
// everything is now non-modal, because modal dialog caused weird high CPU usage on opening
// and tearing of text input
modal: false,
width: 400, width: 400,
open: () => { open: () => {
if (!modal) { if (!modal) {
@@ -80,11 +83,10 @@ async function setupProtectedSession() {
$noteDetailWrapper.show(); $noteDetailWrapper.show();
protectedSessionDeferred.resolve(); protectedSessionDeferred.resolve();
protectedSessionDeferred = null;
$protectedSessionOnButton.addClass('active'); $protectedSessionOnButton.addClass('active');
$protectedSessionOffButton.removeClass('active'); $protectedSessionOffButton.removeClass('active');
protectedSessionDeferred = null;
} }
} }
@@ -105,6 +107,10 @@ async function enterProtectedSessionOnServer(password) {
} }
async function protectNoteAndSendToServer() { async function protectNoteAndSendToServer() {
if (noteDetailService.getCurrentNote().isProtected) {
return;
}
await ensureProtectedSession(true, true); await ensureProtectedSession(true, true);
const note = noteDetailService.getCurrentNote(); const note = noteDetailService.getCurrentNote();
@@ -118,6 +124,10 @@ async function protectNoteAndSendToServer() {
} }
async function unprotectNoteAndSendToServer() { async function unprotectNoteAndSendToServer() {
if (!noteDetailService.getCurrentNote().isProtected) {
return;
}
await ensureProtectedSession(true, true); await ensureProtectedSession(true, true);
const note = noteDetailService.getCurrentNote(); const note = noteDetailService.getCurrentNote();

View File

@@ -1,13 +1,11 @@
import utils from "./utils.js"; import utils from "./utils.js";
import server from "./server.js"; import optionsInitService from './options_init.js';
let lastProtectedSessionOperationDate = null; let lastProtectedSessionOperationDate = null;
let protectedSessionTimeout = null; let protectedSessionTimeout = null;
let protectedSessionId = null; let protectedSessionId = null;
$(document).ready(() => { optionsInitService.optionsReady.then(options => protectedSessionTimeout = options.protectedSessionTimeout);
server.get('options').then(options => protectedSessionTimeout = options.protectedSessionTimeout);
});
setInterval(() => { setInterval(() => {
if (lastProtectedSessionOperationDate !== null && new Date().getTime() - lastProtectedSessionOperationDate.getTime() > protectedSessionTimeout * 1000) { if (lastProtectedSessionOperationDate !== null && new Date().getTime() - lastProtectedSessionOperationDate.getTime() > protectedSessionTimeout * 1000) {

View File

@@ -1,5 +1,6 @@
import treeService from './tree.js'; import treeService from './tree.js';
import server from './server.js'; import server from './server.js';
import treeUtils from "./tree_utils.js";
const $tree = $("#tree"); const $tree = $("#tree");
const $searchInput = $("input[name='search-text']"); const $searchInput = $("input[name='search-text']");
@@ -7,40 +8,62 @@ const $resetSearchButton = $("#reset-search-button");
const $doSearchButton = $("#do-search-button"); const $doSearchButton = $("#do-search-button");
const $saveSearchButton = $("#save-search-button"); const $saveSearchButton = $("#save-search-button");
const $searchBox = $("#search-box"); const $searchBox = $("#search-box");
const $searchResults = $("#search-results");
const $searchResultsInner = $("#search-results-inner");
const $closeSearchButton = $("#close-search-button");
function showSearch() {
$searchBox.show();
$searchInput.focus();
}
function hideSearch() {
resetSearch();
$searchResults.hide();
$searchBox.hide();
}
function toggleSearch() { function toggleSearch() {
if ($searchBox.is(":hidden")) { if ($searchBox.is(":hidden")) {
$searchBox.show(); showSearch();
$searchInput.focus();
} }
else { else {
resetSearch(); hideSearch();
$searchBox.hide();
} }
} }
function resetSearch() { function resetSearch() {
$searchInput.val(""); $searchInput.val("");
getTree().clearFilter();
} }
function getTree() { function getTree() {
return $tree.fancytree('getTree'); return $tree.fancytree('getTree');
} }
async function doSearch() { async function doSearch(searchText) {
const searchText = $searchInput.val(); if (searchText) {
$searchInput.val(searchText);
const noteIds = await server.get('search/' + encodeURIComponent(searchText)); }
else {
for (const noteId of noteIds) { searchText = $searchInput.val();
await treeService.expandToNote(noteId, {noAnimation: true, noEvents: true});
} }
// Pass a string to perform case insensitive matching const results = await server.get('search/' + encodeURIComponent(searchText));
getTree().filterBranches(node => noteIds.includes(node.data.noteId));
$searchResultsInner.empty();
$searchResults.show();
for (const result of results) {
const link = $('<a>', {
href: 'javascript:',
text: result.title
}).attr('action', 'note').attr('note-path', result.path);
const $result = $('<li>').append(link);
$searchResultsInner.append($result);
}
} }
async function saveSearch() { async function saveSearch() {
@@ -71,6 +94,11 @@ $resetSearchButton.click(resetSearch);
$saveSearchButton.click(saveSearch); $saveSearchButton.click(saveSearch);
$closeSearchButton.click(hideSearch);
export default { export default {
toggleSearch toggleSearch,
resetSearch,
showSearch,
doSearch
}; };

View File

@@ -5,7 +5,7 @@ import infoService from "./info.js";
function getHeaders() { function getHeaders() {
let protectedSessionId = null; let protectedSessionId = null;
try { // this is because protected session might not be declared in some cases - like when it's included in migration page try { // this is because protected session might not be declared in some cases
protectedSessionId = protectedSessionHolder.getProtectedSessionId(); protectedSessionId = protectedSessionHolder.getProtectedSessionId();
} }
catch(e) {} catch(e) {}

View File

@@ -183,7 +183,7 @@ async function getRunPath(notePath) {
return effectivePath.reverse(); return effectivePath.reverse();
} }
async function showParentList(noteId, node) { async function showPaths(noteId, node) {
utils.assertArguments(noteId, node); utils.assertArguments(noteId, node);
const note = await treeCache.getNote(noteId); const note = await treeCache.getNote(noteId);
@@ -191,10 +191,6 @@ async function showParentList(noteId, node) {
$notePathCount.html(parents.length + " path" + (parents.length > 0 ? "s" : "")); $notePathCount.html(parents.length + " path" + (parents.length > 0 ? "s" : ""));
if (parents.length <= 1) {
}
else {
//$notePathList.show();
$notePathList.empty(); $notePathList.empty();
for (const parentNote of parents) { for (const parentNote of parents) {
@@ -211,7 +207,6 @@ async function showParentList(noteId, node) {
$notePathList.append(item); $notePathList.append(item);
} }
}
} }
async function getSomeNotePath(note) { async function getSomeNotePath(note) {
@@ -323,7 +318,7 @@ function initFancyTree(tree) {
noteDetailService.switchToNote(node.noteId); noteDetailService.switchToNote(node.noteId);
showParentList(node.noteId, data.node); showPaths(node.noteId, data.node);
}, },
expand: (event, data) => setExpandedToServer(data.node.data.branchId, true), expand: (event, data) => setExpandedToServer(data.node.data.branchId, true),
collapse: (event, data) => setExpandedToServer(data.node.data.branchId, false), collapse: (event, data) => setExpandedToServer(data.node.data.branchId, false),

View File

@@ -74,14 +74,21 @@ async function prepareRealBranch(parentNote) {
async function prepareSearchBranch(note) { async function prepareSearchBranch(note) {
const fullNote = await noteDetailService.loadNote(note.noteId); const fullNote = await noteDetailService.loadNote(note.noteId);
const noteIds = await server.get('search/' + encodeURIComponent(fullNote.jsonContent.searchString)); const results = await server.get('search/' + encodeURIComponent(fullNote.jsonContent.searchString));
const noteIds = results.map(res => res.noteId);
// force to load all the notes at once instead of one by one
await treeCache.getNotes(noteIds);
for (const result of results) {
const origBranch = await treeCache.getBranch(result.branchId);
for (const noteId of noteIds) {
const branch = new Branch(treeCache, { const branch = new Branch(treeCache, {
branchId: "virt" + utils.randomString(10), branchId: "virt" + utils.randomString(10),
noteId: noteId, noteId: result.noteId,
parentNoteId: note.noteId, parentNoteId: note.noteId,
prefix: '', prefix: origBranch.prefix,
virtual: true virtual: true
}); });

View File

@@ -52,6 +52,15 @@ async function getNotePathTitle(notePath) {
const titlePath = []; const titlePath = [];
if (notePath.startsWith('root/')) {
notePath = notePath.substr(5);
}
// special case when we want just root's title
if (notePath === 'root') {
return await getNoteTitle(notePath);
}
let parentNoteId = 'root'; let parentNoteId = 'root';
for (const noteId of notePath.split('/')) { for (const noteId of notePath.split('/')) {

View File

@@ -0,0 +1,51 @@
import server from "./server.js";
import utils from "./utils.js";
import optionsInitService from "./options_init.js";
const MIN_ZOOM = 0.5;
const MAX_ZOOM = 2.0;
async function decreaseZoomFactor() {
await setZoomFactorAndSave(getCurrentZoom() - 0.1);
}
async function increaseZoomFactor() {
await setZoomFactorAndSave(getCurrentZoom() + 0.1);
}
function setZoomFactor(zoomFactor) {
zoomFactor = parseFloat(zoomFactor);
const webFrame = require('electron').webFrame;
webFrame.setZoomFactor(zoomFactor);
}
async function setZoomFactorAndSave(zoomFactor) {
if (!utils.isElectron()) {
return;
}
if (zoomFactor >= MIN_ZOOM && zoomFactor <= MAX_ZOOM) {
setZoomFactor(zoomFactor);
await server.put('options/zoomFactor/' + zoomFactor);
}
else {
console.log(`Zoom factor ${zoomFactor} outside of the range, ignored.`);
}
}
function getCurrentZoom() {
return require('electron').webFrame.getZoomFactor();
}
if (utils.isElectron()) {
optionsInitService.optionsReady.then(options => setZoomFactor(options.zoomFactor))
}
export default {
decreaseZoomFactor,
increaseZoomFactor,
setZoomFactor,
setZoomFactorAndSave
}

File diff suppressed because one or more lines are too long

View File

@@ -20,6 +20,7 @@
background-color: #f1f1f1; background-color: #f1f1f1;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 4px;
} }
#note-detail-wrapper { #note-detail-wrapper {
@@ -51,7 +52,7 @@
overflow: auto; overflow: auto;
} }
#note-detail-wrapper.protected, #note-detail-wrapper.protected .CodeMirror { #note-detail-component-wrapper.protected, #note-detail-component-wrapper.protected .CodeMirror {
background-color: #eee; background-color: #eee;
} }
@@ -66,31 +67,31 @@ ul.fancytree-container {
/* icons from https://feathericons.com */ /* icons from https://feathericons.com */
span.fancytree-node > span.fancytree-icon { span.fancytree-node > span.fancytree-icon {
background: url("../images/icons/file.png") 0 0; background: url("../images/icons/file-16.png") 0 0;
} }
span.fancytree-node.fancytree-folder > span.fancytree-icon { span.fancytree-node.fancytree-folder > span.fancytree-icon {
background: url("../images/icons/folder.png") 0 0; background: url("../images/icons/folder-16.png") 0 0;
} }
span.fancytree-node.code > span.fancytree-icon { span.fancytree-node.code > span.fancytree-icon {
background: url("../images/icons/code.png") 0 0; background: url("../images/icons/code-16.png") 0 0;
} }
span.fancytree-node.fancytree-folder.code > span.fancytree-icon { span.fancytree-node.fancytree-folder.code > span.fancytree-icon {
background: url("../images/icons/code-folder.png") 0 0; background: url("../images/icons/code-folder-16.png") 0 0;
} }
span.fancytree-node.file > span.fancytree-icon { span.fancytree-node.file > span.fancytree-icon {
background: url("../images/icons/paperclip.png") 0 0; background: url("../images/icons/paperclip-16.png") 0 0;
} }
span.fancytree-node.render > span.fancytree-icon { span.fancytree-node.render > span.fancytree-icon {
background: url("../images/icons/play.png") 0 0; background: url("../images/icons/play-16.png") 0 0;
} }
span.fancytree-node.search > span.fancytree-icon { span.fancytree-node.search > span.fancytree-icon {
background: url("../images/icons/search-small.png") 0 0; background: url("../images/icons/search-small-16.png") 0 0;
} }
span.fancytree-node.protected > span.fancytree-icon { span.fancytree-node.protected > span.fancytree-icon {
@@ -106,7 +107,7 @@ span.fancytree-node.fancytree-active-clone:not(.fancytree-active) .fancytree-tit
} }
span.fancytree-node.tree-root > span.fancytree-icon { span.fancytree-node.tree-root > span.fancytree-icon {
background: url("../images/icons/tree-root.png") 0 0; background: url("../images/icons/tree-root-16.png") 0 0;
} }
/* first nesting level has lower left padding to avoid extra left padding. Other levels are not affected */ /* first nesting level has lower left padding to avoid extra left padding. Other levels are not affected */
@@ -138,10 +139,6 @@ span.fancytree-active:not(.fancytree-focused) .fancytree-title {
width: 24px; width: 24px;
} }
#protect-button, #unprotect-button {
display: none;
}
.ui-widget-content a:not(.ui-tabs-anchor) { .ui-widget-content a:not(.ui-tabs-anchor) {
color: #337ab7 !important; color: #337ab7 !important;
} }
@@ -170,11 +167,27 @@ div.ui-tooltip {
#tree { #tree {
overflow: auto; overflow: auto;
flex-grow: 100; flex-grow: 1;
flex-shrink: 100; flex-shrink: 1;
flex-basis: 60%;
margin-top: 10px; margin-top: 10px;
} }
#search-results {
padding: 0 5px 5px 15px;
flex-basis: 40%;
flex-grow: 1;
flex-shrink: 1;
margin-top: 10px;
display: none;
overflow: auto;
border-bottom: 2px solid #ddd;
}
#search-results ul {
padding: 5px 5px 5px 15px;
}
/* /*
* .electron-in-page-search-window is a class specified to default * .electron-in-page-search-window is a class specified to default
* <webview> element for search window. * <webview> element for search window.
@@ -232,7 +245,7 @@ div.ui-tooltip {
} }
.suppressed { .suppressed {
filter: opacity(7%); display: none;
} }
#note-type .dropdown-menu li:not(.divider) { #note-type .dropdown-menu li:not(.divider) {
@@ -241,7 +254,7 @@ div.ui-tooltip {
} }
.dropdown-menu li:not(.divider):hover, .dropdown-menu li:not(.divider) a:hover { .dropdown-menu li:not(.divider):hover, .dropdown-menu li:not(.divider) a:hover {
background-color: #eee !important; background-color: #ccc !important;
cursor: pointer; cursor: pointer;
} }
@@ -359,14 +372,52 @@ div.ui-tooltip {
display: flex; display: flex;
} }
.btn { .btn:not(.btn-primary):not(.btn-danger) {
border-color: #ddd; border-color: #bbb;
background-color: #eee;
} }
.btn.active { .btn.active:not(.btn-primary) {
background-color: #ddd; background-color: #ccc;
} }
#note-path-list .current a { #note-path-list .current a {
font-weight: bold; font-weight: bold;
} }
button.icon-button {
height: 28px;
width: 28px;
background: no-repeat center;
}
#note-actions {
margin-left: 10px;
margin-right: 10px;
}
#note-actions .dropdown-menu {
width: 15em;
}
/* Themes */
html.theme-black, html.theme-black img, html.theme-black video {
filter: invert(100%) hue-rotate(180deg);
}
html.theme-black body {
background: black;
}
html.theme-dark {
filter: invert(90%) hue-rotate(180deg);
}
html.theme-dark img, html.theme-dark video {
filter: invert(100%) hue-rotate(180deg);
}
html.theme-dark body {
background: #191819;
}

View File

@@ -1,16 +1,16 @@
"use strict"; "use strict";
const autocompleteService = require('../../services/autocomplete'); const noteCacheService = require('../../services/note_cache');
async function getAutocomplete(req) { async function getAutocomplete(req) {
const query = req.query.query; const query = req.query.query;
const results = autocompleteService.getResults(query); const results = noteCacheService.findNotes(query);
return results.map(res => { return results.map(res => {
return { return {
value: res.title + ' (' + res.path + ')', value: res.title + ' (' + res.path + ')',
title: res.title label: res.title
} }
}); });
} }

View File

@@ -21,10 +21,10 @@ async function loginSync(req) {
return [400, { message: 'Auth request time is out of sync' }]; return [400, { message: 'Auth request time is out of sync' }];
} }
const dbVersion = req.body.dbVersion; const syncVersion = req.body.syncVersion;
if (dbVersion !== appInfo.dbVersion) { if (syncVersion !== appInfo.syncVersion) {
return [400, { message: 'Non-matching db versions, local is version ' + appInfo.dbVersion }]; return [400, { message: 'Non-matching sync versions, local is version ' + appInfo.syncVersion }];
} }
const documentSecret = await options.getOption('documentSecret'); const documentSecret = await options.getOption('documentSecret');

View File

@@ -1,25 +0,0 @@
"use strict";
const optionService = require('../../services/options');
const migrationService = require('../../services/migration');
const appInfo = require('../../services/app_info');
async function getMigrationInfo() {
return {
dbVersion: parseInt(await optionService.getOption('dbVersion')),
appDbVersion: appInfo.dbVersion
};
}
async function executeMigration() {
const migrations = await migrationService.migrate();
return {
migrations: migrations
};
}
module.exports = {
getMigrationInfo,
executeMigration
};

View File

@@ -2,9 +2,10 @@
const sql = require('../../services/sql'); const sql = require('../../services/sql');
const optionService = require('../../services/options'); const optionService = require('../../services/options');
const log = require('../../services/log');
// options allowed to be updated directly in options dialog // options allowed to be updated directly in options dialog
const ALLOWED_OPTIONS = ['protectedSessionTimeout', 'noteRevisionSnapshotTimeInterval']; const ALLOWED_OPTIONS = ['protectedSessionTimeout', 'noteRevisionSnapshotTimeInterval', 'zoomFactor', 'theme'];
async function getOptions() { async function getOptions() {
const options = await sql.getMap("SELECT name, value FROM options WHERE name IN (" const options = await sql.getMap("SELECT name, value FROM options WHERE name IN ("
@@ -20,6 +21,8 @@ async function updateOption(req) {
return [400, "not allowed option to set"]; return [400, "not allowed option to set"];
} }
log.info(`Updating option ${name} to ${value}`);
await optionService.setOption(name, value); await optionService.setOption(name, value);
} }

View File

@@ -1,12 +1,12 @@
"use strict"; "use strict";
const repository = require('../../services/repository'); const repository = require('../../services/repository');
const dateUtils = require('../../services/date_utils');
const optionService = require('../../services/options'); const optionService = require('../../services/options');
const RecentNote = require('../../entities/recent_note'); const RecentNote = require('../../entities/recent_note');
const noteCacheService = require('../../services/note_cache');
async function getRecentNotes() { async function getRecentNotes() {
return await repository.getEntities(` const recentNotes = await repository.getEntities(`
SELECT SELECT
recent_notes.* recent_notes.*
FROM FROM
@@ -18,6 +18,12 @@ async function getRecentNotes() {
ORDER BY ORDER BY
dateCreated DESC dateCreated DESC
LIMIT 200`); LIMIT 200`);
for (const rn of recentNotes) {
rn.title = noteCacheService.getNoteTitleForPath(rn.notePath.split('/'));
}
return recentNotes;
} }
async function addRecentNote(req) { async function addRecentNote(req) {

View File

@@ -2,15 +2,69 @@
const sql = require('../../services/sql'); const sql = require('../../services/sql');
const noteService = require('../../services/notes'); const noteService = require('../../services/notes');
const noteCacheService = require('../../services/note_cache');
const parseFilters = require('../../services/parse_filters'); const parseFilters = require('../../services/parse_filters');
const buildSearchQuery = require('../../services/build_search_query'); const buildSearchQuery = require('../../services/build_search_query');
async function searchNotes(req) { async function searchNotes(req) {
const {labelFilters, searchText} = parseFilters(req.params.searchString); const {labelFilters, searchText} = parseFilters(req.params.searchString);
let labelFiltersNoteIds = null;
if (labelFilters.length > 0) {
const {query, params} = buildSearchQuery(labelFilters, searchText); const {query, params} = buildSearchQuery(labelFilters, searchText);
const noteIds = await sql.getColumn(query, params); labelFiltersNoteIds = await sql.getColumn(query, params);
}
let searchTextResults = null;
if (searchText.trim().length > 0) {
searchTextResults = noteCacheService.findNotes(searchText);
let fullTextNoteIds = await getFullTextResults(searchText);
for (const noteId of fullTextNoteIds) {
if (!searchTextResults.some(item => item.noteId === noteId)) {
const result = noteCacheService.getNotePath(noteId);
if (result) {
searchTextResults.push(result);
}
}
}
}
let results;
if (labelFiltersNoteIds && searchTextResults) {
results = labelFiltersNoteIds.filter(item => searchTextResults.includes(item.noteId));
}
else if (labelFiltersNoteIds) {
results = labelFiltersNoteIds.map(noteCacheService.getNotePath).filter(res => !!res);
}
else {
results = searchTextResults;
}
return results;
}
async function getFullTextResults(searchText) {
const tokens = searchText.toLowerCase().split(" ");
const tokenSql = ["1=1"];
for (const token of tokens) {
// FIXME: escape token!
tokenSql.push(`(title LIKE '%${token}%' OR content LIKE '%${token}%')`);
}
const noteIds = await sql.getColumn(`
SELECT DISTINCT noteId
FROM notes
WHERE isDeleted = 0
AND isProtected = 0
AND ${tokenSql.join(' AND ')}`);
return noteIds; return noteIds;
} }
@@ -20,7 +74,7 @@ async function saveSearchToNote(req) {
searchString: req.params.searchString searchString: req.params.searchString
}; };
const {note} = await noteService.createNote('root', 'Search note', noteContent, { const {note} = await noteService.createNote('root', req.params.searchString, noteContent, {
json: true, json: true,
type: 'search', type: 'search',
mime: "application/json" mime: "application/json"

View File

@@ -4,9 +4,11 @@ const sourceIdService = require('../services/source_id');
const sql = require('../services/sql'); const sql = require('../services/sql');
const labelService = require('../services/labels'); const labelService = require('../services/labels');
const config = require('../services/config'); const config = require('../services/config');
const optionService = require('../services/options');
async function index(req, res) { async function index(req, res) {
res.render('index', { res.render('index', {
theme: await optionService.getOption('theme'),
sourceId: await sourceIdService.generateSourceId(), sourceId: await sourceIdService.generateSourceId(),
maxSyncIdAtLoad: await sql.getValue("SELECT MAX(id) FROM sync"), maxSyncIdAtLoad: await sql.getValue("SELECT MAX(id) FROM sync"),
instanceName: config.General ? config.General.instanceName : null, instanceName: config.General ? config.General.instanceName : null,

View File

@@ -1,9 +0,0 @@
"use strict";
function migrationPage(req, res) {
res.render('migration', {});
}
module.exports = {
migrationPage
};

View File

@@ -1,6 +1,5 @@
const indexRoute = require('./index'); const indexRoute = require('./index');
const loginRoute = require('./login'); const loginRoute = require('./login');
const migrationRoute = require('./migration');
const setupRoute = require('./setup'); const setupRoute = require('./setup');
const multer = require('multer')(); const multer = require('multer')();
@@ -14,7 +13,6 @@ const noteRevisionsApiRoute = require('./api/note_revisions');
const recentChangesApiRoute = require('./api/recent_changes'); const recentChangesApiRoute = require('./api/recent_changes');
const optionsApiRoute = require('./api/options'); const optionsApiRoute = require('./api/options');
const passwordApiRoute = require('./api/password'); const passwordApiRoute = require('./api/password');
const migrationApiRoute = require('./api/migration');
const syncApiRoute = require('./api/sync'); const syncApiRoute = require('./api/sync');
const loginApiRoute = require('./api/login'); const loginApiRoute = require('./api/login');
const eventLogRoute = require('./api/event_log'); const eventLogRoute = require('./api/event_log');
@@ -96,7 +94,6 @@ function register(app) {
route(GET, '/login', [], loginRoute.loginPage); route(GET, '/login', [], loginRoute.loginPage);
route(POST, '/login', [], loginRoute.login); route(POST, '/login', [], loginRoute.login);
route(POST, '/logout', [auth.checkAuth], loginRoute.logout); route(POST, '/logout', [auth.checkAuth], loginRoute.logout);
route(GET, '/migration', [auth.checkAuthForMigrationPage], migrationRoute.migrationPage);
route(GET, '/setup', [auth.checkAppNotInitialized], setupRoute.setupPage); route(GET, '/setup', [auth.checkAppNotInitialized], setupRoute.setupPage);
apiRoute(GET, '/api/tree', treeApiRoute.getTree); apiRoute(GET, '/api/tree', treeApiRoute.getTree);
@@ -180,9 +177,6 @@ function register(app) {
apiRoute(GET, '/api/search/:searchString', searchRoute.searchNotes); apiRoute(GET, '/api/search/:searchString', searchRoute.searchNotes);
apiRoute(POST, '/api/search/:searchString', searchRoute.saveSearchToNote); apiRoute(POST, '/api/search/:searchString', searchRoute.saveSearchToNote);
route(GET, '/api/migration', [auth.checkApiAuthForMigrationPage], migrationApiRoute.getMigrationInfo, apiResultHandler);
route(POST, '/api/migration', [auth.checkApiAuthForMigrationPage], migrationApiRoute.executeMigration, apiResultHandler);
route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler); route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler);
// this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username) // this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username)
apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession); apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession);

Binary file not shown.

View File

@@ -3,11 +3,13 @@
const build = require('./build'); const build = require('./build');
const packageJson = require('../../package'); const packageJson = require('../../package');
const APP_DB_VERSION = 96; const APP_DB_VERSION = 100;
const SYNC_VERSION = 1;
module.exports = { module.exports = {
appVersion: packageJson.version, appVersion: packageJson.version,
dbVersion: APP_DB_VERSION, dbVersion: APP_DB_VERSION,
syncVersion: SYNC_VERSION,
buildDate: build.buildDate, buildDate: build.buildDate,
buildRevision: build.buildRevision buildRevision: build.buildRevision
}; };

View File

@@ -12,18 +12,6 @@ async function checkAuth(req, res, next) {
else if (!req.session.loggedIn && !utils.isElectron()) { else if (!req.session.loggedIn && !utils.isElectron()) {
res.redirect("login"); res.redirect("login");
} }
else if (!await sqlInit.isDbUpToDate()) {
res.redirect("migration");
}
else {
next();
}
}
async function checkAuthForMigrationPage(req, res, next) {
if (!req.session.loggedIn && !utils.isElectron()) {
res.redirect("login");
}
else { else {
next(); next();
} }
@@ -35,27 +23,12 @@ async function checkApiAuthOrElectron(req, res, next) {
if (!req.session.loggedIn && !utils.isElectron()) { if (!req.session.loggedIn && !utils.isElectron()) {
res.status(401).send("Not authorized"); res.status(401).send("Not authorized");
} }
else if (await sqlInit.isDbUpToDate()) {
next();
}
else { else {
res.status(409).send("Mismatched app versions"); // need better response than that next();
} }
} }
async function checkApiAuth(req, res, next) { async function checkApiAuth(req, res, next) {
if (!req.session.loggedIn) {
res.status(401).send("Not authorized");
}
else if (await sqlInit.isDbUpToDate()) {
next();
}
else {
res.status(409).send("Mismatched app versions"); // need better response than that
}
}
async function checkApiAuthForMigrationPage(req, res, next) {
if (!req.session.loggedIn) { if (!req.session.loggedIn) {
res.status(401).send("Not authorized"); res.status(401).send("Not authorized");
} }
@@ -79,19 +52,14 @@ async function checkSenderToken(req, res, next) {
if (await sql.getValue("SELECT COUNT(*) FROM api_tokens WHERE isDeleted = 0 AND token = ?", [token]) === 0) { if (await sql.getValue("SELECT COUNT(*) FROM api_tokens WHERE isDeleted = 0 AND token = ?", [token]) === 0) {
res.status(401).send("Not authorized"); res.status(401).send("Not authorized");
} }
else if (await sqlInit.isDbUpToDate()) {
next();
}
else { else {
res.status(409).send("Mismatched app versions"); // need better response than that next();
} }
} }
module.exports = { module.exports = {
checkAuth, checkAuth,
checkAuthForMigrationPage,
checkApiAuth, checkApiAuth,
checkApiAuthForMigrationPage,
checkAppNotInitialized, checkAppNotInitialized,
checkApiAuthOrElectron, checkApiAuthOrElectron,
checkSenderToken checkSenderToken

View File

@@ -1 +1 @@
module.exports = { buildDate:"2018-05-31T23:23:44-04:00", buildRevision: "80d2457b23b916b869fe2a91ae4e65e33ab42549" }; module.exports = { buildDate:"2018-06-16T13:34:39-04:00", buildRevision: "3a95c9e1bc4d608a2135c9412732c2c7fbb2834c" };

View File

@@ -1,4 +1,4 @@
module.exports = function(labelFilters, searchText) { module.exports = function(labelFilters) {
const joins = []; const joins = [];
const joinParams = []; const joinParams = [];
let where = '1'; let where = '1';
@@ -44,16 +44,6 @@ module.exports = function(labelFilters, searchText) {
let searchCondition = ''; let searchCondition = '';
const searchParams = []; const searchParams = [];
if (searchText.trim() !== '') {
// searching in protected notes is pointless because of encryption
searchCondition = ' AND (notes.isProtected = 0 AND (notes.title LIKE ? OR notes.content LIKE ?))';
searchText = '%' + searchText.trim() + '%';
searchParams.push(searchText);
searchParams.push(searchText); // two occurences in searchCondition
}
const query = `SELECT DISTINCT notes.noteId FROM notes const query = `SELECT DISTINCT notes.noteId FROM notes
${joins.join('\r\n')} ${joins.join('\r\n')}
WHERE WHERE

View File

@@ -16,8 +16,11 @@ const RecentNote = require('../entities/recent_note');
const Option = require('../entities/option'); const Option = require('../entities/option');
async function getHash(entityConstructor, whereBranch) { async function getHash(entityConstructor, whereBranch) {
let contentToHash = await sql.getValue(`SELECT GROUP_CONCAT(hash) FROM ${entityConstructor.tableName} ` // subselect is necessary to have correct ordering in GROUP_CONCAT
+ (whereBranch ? `WHERE ${whereBranch} ` : '') + `ORDER BY ${entityConstructor.primaryKeyName}`); const query = `SELECT GROUP_CONCAT(hash) FROM (SELECT hash FROM ${entityConstructor.tableName} `
+ (whereBranch ? `WHERE ${whereBranch} ` : '') + `ORDER BY ${entityConstructor.primaryKeyName})`;
let contentToHash = await sql.getValue(query);
if (!contentToHash) { // might be null in case of no rows if (!contentToHash) { // might be null in case of no rows
contentToHash = ""; contentToHash = "";

View File

@@ -6,7 +6,7 @@ const Label = require('../entities/label');
const BUILTIN_LABELS = [ const BUILTIN_LABELS = [
'disableVersioning', 'disableVersioning',
'calendarRoot', 'calendarRoot',
'hideInAutocomplete', 'archived',
'excludeFromExport', 'excludeFromExport',
'run', 'run',
'manualTransactionHandling', 'manualTransactionHandling',

View File

@@ -8,8 +8,9 @@ const utils = require('./utils');
let noteTitles; let noteTitles;
let protectedNoteTitles; let protectedNoteTitles;
let noteIds; let noteIds;
let childParentToBranchId = {};
const childToParent = {}; const childToParent = {};
const hideInAutocomplete = {}; const archived = {};
// key is 'childNoteId-parentNoteId' as a replacement for branchId which we don't use here // key is 'childNoteId-parentNoteId' as a replacement for branchId which we don't use here
let prefixes = {}; let prefixes = {};
@@ -20,21 +21,22 @@ async function load() {
prefixes = await sql.getMap(`SELECT noteId || '-' || parentNoteId, prefix FROM branches WHERE prefix IS NOT NULL AND prefix != ''`); prefixes = await sql.getMap(`SELECT noteId || '-' || parentNoteId, prefix FROM branches WHERE prefix IS NOT NULL AND prefix != ''`);
const relations = await sql.getRows(`SELECT noteId, parentNoteId FROM branches WHERE isDeleted = 0`); const relations = await sql.getRows(`SELECT branchId, noteId, parentNoteId FROM branches WHERE isDeleted = 0`);
for (const rel of relations) { for (const rel of relations) {
childToParent[rel.noteId] = childToParent[rel.noteId] || []; childToParent[rel.noteId] = childToParent[rel.noteId] || [];
childToParent[rel.noteId].push(rel.parentNoteId); childToParent[rel.noteId].push(rel.parentNoteId);
childParentToBranchId[`${rel.noteId}-${rel.parentNoteId}`] = rel.branchId;
} }
const hiddenLabels = await sql.getColumn(`SELECT noteId FROM labels WHERE isDeleted = 0 AND name = 'hideInAutocomplete'`); const hiddenLabels = await sql.getColumn(`SELECT noteId FROM labels WHERE isDeleted = 0 AND name = 'archived'`);
for (const noteId of hiddenLabels) { for (const noteId of hiddenLabels) {
hideInAutocomplete[noteId] = true; archived[noteId] = true;
} }
} }
function getResults(query) { function findNotes(query) {
if (!noteTitles || query.length <= 2) { if (!noteTitles || query.length <= 2) {
return []; return [];
} }
@@ -49,7 +51,7 @@ function getResults(query) {
} }
for (const noteId of noteIds) { for (const noteId of noteIds) {
if (hideInAutocomplete[noteId]) { if (archived[noteId]) {
continue; continue;
} }
@@ -59,7 +61,7 @@ function getResults(query) {
} }
for (const parentNoteId of parents) { for (const parentNoteId of parents) {
if (hideInAutocomplete[parentNoteId]) { if (archived[parentNoteId]) {
continue; continue;
} }
@@ -91,8 +93,12 @@ function search(noteId, tokens, path, results) {
if (retPath) { if (retPath) {
const noteTitle = getNoteTitleForPath(retPath); const noteTitle = getNoteTitleForPath(retPath);
const thisNoteId = retPath[retPath.length - 1];
const thisParentNoteId = retPath[retPath.length - 2];
results.push({ results.push({
noteId: thisNoteId,
branchId: childParentToBranchId[`${thisNoteId}-${thisParentNoteId}`],
title: noteTitle, title: noteTitle,
path: retPath.join('/') path: retPath.join('/')
}); });
@@ -111,7 +117,7 @@ function search(noteId, tokens, path, results) {
return; return;
} }
if (parentNoteId === 'root' || hideInAutocomplete[parentNoteId]) { if (parentNoteId === 'root' || archived[parentNoteId]) {
continue; continue;
} }
@@ -155,6 +161,15 @@ function getNoteTitle(noteId, parentNoteId) {
function getNoteTitleForPath(path) { function getNoteTitleForPath(path) {
const titles = []; const titles = [];
if (path[0] === 'root') {
if (path.length === 1) {
return getNoteTitle('root');
}
else {
path = path.slice(1);
}
}
let parentNoteId = 'root'; let parentNoteId = 'root';
for (const noteId of path) { for (const noteId of path) {
@@ -180,6 +195,10 @@ function getSomePath(noteId, path) {
} }
for (const parentNoteId of parents) { for (const parentNoteId of parents) {
if (archived[parentNoteId]) {
continue;
}
const retPath = getSomePath(parentNoteId, path.concat([noteId])); const retPath = getSomePath(parentNoteId, path.concat([noteId]));
if (retPath) { if (retPath) {
@@ -190,6 +209,22 @@ function getSomePath(noteId, path) {
return false; return false;
} }
function getNotePath(noteId) {
const retPath = getSomePath(noteId, []);
if (retPath) {
const noteTitle = getNoteTitleForPath(retPath);
const parentNoteId = childToParent[noteId][0];
return {
noteId: noteId,
branchId: childParentToBranchId[`${noteId}-${parentNoteId}`],
title: noteTitle,
path: retPath.join('/')
};
}
}
eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entityId}) => { eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entityId}) => {
if (entityName === 'notes') { if (entityName === 'notes') {
const note = await repository.getNote(entityId); const note = await repository.getNote(entityId);
@@ -211,6 +246,7 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entityId
if (branch.isDeleted) { if (branch.isDeleted) {
delete prefixes[branch.noteId + '-' + branch.parentNoteId]; delete prefixes[branch.noteId + '-' + branch.parentNoteId];
delete childParentToBranchId[branch.noteId + '-' + branch.parentNoteId];
} }
else { else {
if (branch.prefix) { if (branch.prefix) {
@@ -219,21 +255,22 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entityId
childToParent[branch.noteId] = childToParent[branch.noteId] || []; childToParent[branch.noteId] = childToParent[branch.noteId] || [];
childToParent[branch.noteId].push(branch.parentNoteId); childToParent[branch.noteId].push(branch.parentNoteId);
childParentToBranchId[branch.noteId + '-' + branch.parentNoteId] = branch.branchId;
} }
} }
else if (entityName === 'labels') { else if (entityName === 'labels') {
const label = await repository.getLabel(entityId); const label = await repository.getLabel(entityId);
if (label.name === 'hideInAutocomplete') { if (label.name === 'archived') {
// we're not using label object directly, since there might be other non-deleted hideInAutocomplete label // we're not using label object directly, since there might be other non-deleted archived label
const hideLabel = await repository.getEntity(`SELECT * FROM labels WHERE isDeleted = 0 const hideLabel = await repository.getEntity(`SELECT * FROM labels WHERE isDeleted = 0
AND name = 'hideInAutocomplete' AND noteId = ?`, [label.noteId]); AND name = 'archived' AND noteId = ?`, [label.noteId]);
if (hideLabel) { if (hideLabel) {
hideInAutocomplete[label.noteId] = true; archived[label.noteId] = true;
} }
else { else {
delete hideInAutocomplete[label.noteId]; delete archived[label.noteId];
} }
} }
} }
@@ -250,5 +287,7 @@ eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, async () => {
sqlInit.dbReady.then(() => utils.stopWatch("Autocomplete load", load)); sqlInit.dbReady.then(() => utils.stopWatch("Autocomplete load", load));
module.exports = { module.exports = {
getResults findNotes,
getNotePath,
getNoteTitleForPath
}; };

View File

@@ -2,7 +2,6 @@ const repository = require('./repository');
const utils = require('./utils'); const utils = require('./utils');
const dateUtils = require('./date_utils'); const dateUtils = require('./date_utils');
const appInfo = require('./app_info'); const appInfo = require('./app_info');
const Option = require('../entities/option');
async function getOption(name) { async function getOption(name) {
const option = await repository.getOption(name); const option = await repository.getOption(name);
@@ -27,6 +26,9 @@ async function setOption(name, value) {
} }
async function createOption(name, value, isSynced) { async function createOption(name, value, isSynced) {
// to avoid circular dependency, need to find better solution
const Option = require('../entities/option');
await new Option({ await new Option({
name: name, name: name,
value: value, value: value,
@@ -53,6 +55,9 @@ async function initOptions(startNotePath) {
await createOption('lastSyncedPull', appInfo.dbVersion, false); await createOption('lastSyncedPull', appInfo.dbVersion, false);
await createOption('lastSyncedPush', 0, false); await createOption('lastSyncedPush', 0, false);
await createOption('zoomFactor', 1.0, false);
await createOption('theme', 'white', false);
} }
module.exports = { module.exports = {

View File

@@ -42,7 +42,10 @@ const dbReady = new Promise((resolve, reject) => {
} }
if (!await isDbUpToDate()) { if (!await isDbUpToDate()) {
return; // avoiding circular dependency
const migrationService = require('./migration');
await migrationService.migrate();
} }
resolve(db); resolve(db);
@@ -94,6 +97,12 @@ async function isDbUpToDate() {
} }
async function isUserInitialized() { async function isUserInitialized() {
const optionsTable = await sql.getRows("SELECT name FROM sqlite_master WHERE type='table' AND name='options'");
if (optionsTable.length !== 1) {
return false;
}
const username = await sql.getValue("SELECT value FROM options WHERE name = 'username'"); const username = await sql.getValue("SELECT value FROM options WHERE name = 'username'");
return !!username; return !!username;

View File

@@ -22,13 +22,6 @@ let syncServerCertificate = null;
async function sync() { async function sync() {
try { try {
await syncMutexService.doExclusively(async () => { await syncMutexService.doExclusively(async () => {
if (!await sqlInit.isDbUpToDate()) {
return {
success: false,
message: "DB not up to date"
};
}
const syncContext = await login(); const syncContext = await login();
await pushSync(syncContext); await pushSync(syncContext);
@@ -76,7 +69,7 @@ async function login() {
const resp = await syncRequest(syncContext, 'POST', '/api/login/sync', { const resp = await syncRequest(syncContext, 'POST', '/api/login/sync', {
timestamp: timestamp, timestamp: timestamp,
dbVersion: appInfo.dbVersion, syncVersion: appInfo.syncVersion,
hash: hash hash: hash
}); });
@@ -212,7 +205,7 @@ const primaryKeys = {
"note_images": "noteImageId", "note_images": "noteImageId",
"labels": "labelId", "labels": "labelId",
"api_tokens": "apiTokenId", "api_tokens": "apiTokenId",
"options": "optionId" "options": "name"
}; };
async function getEntityRow(entityName, entityId) { async function getEntityRow(entityName, entityId) {

View File

@@ -79,6 +79,32 @@ function stripTags(text) {
return text.replace(/<(?:.|\n)*?>/gm, ''); return text.replace(/<(?:.|\n)*?>/gm, '');
} }
function intersection(a, b) {
return a.filter(value => b.indexOf(value) !== -1);
}
function union(a, b) {
const obj = {};
for (let i = a.length-1; i >= 0; i--) {
obj[a[i]] = a[i];
}
for (let i = b.length-1; i >= 0; i--) {
obj[b[i]] = b[i];
}
const res = [];
for (const k in obj) {
if (obj.hasOwnProperty(k)) { // <-- optional
res.push(obj[k]);
}
}
return res;
}
module.exports = { module.exports = {
randomSecureToken, randomSecureToken,
randomString, randomString,
@@ -93,5 +119,7 @@ module.exports = {
stopWatch, stopWatch,
unescapeHtml, unescapeHtml,
toObject, toObject,
stripTags stripTags,
intersection,
union
}; };

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" class="theme-<%= theme %>">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Trilium Notes</title> <title>Trilium Notes</title>
@@ -7,24 +7,18 @@
<body> <body>
<div id="container" style="display:none;"> <div id="container" style="display:none;">
<div id="header" class="hide-toggle"> <div id="header" class="hide-toggle">
<div id="header-title">
<img src="/images/app-icons/png/24x24.png">
Trilium Notes
</div>
<div id="history-navigation" style="display: none;"> <div id="history-navigation" style="display: none;">
<a id="history-back-button" title="Go to previous note." class="icon-action" <a id="history-back-button" title="Go to previous note." class="icon-action"
style="background: url('/images/icons/back.png')"></a> style="background: url('/images/icons/back-24.png')"></a>
&nbsp; &nbsp; &nbsp; &nbsp;
<a id="history-forward-button" title="Go to next note." class="icon-action" <a id="history-forward-button" title="Go to next note." class="icon-action"
style="background: url('/images/icons/forward.png')"></a> style="background: url('/images/icons/forward-24.png')"></a>
</div> </div>
<div style="flex-grow: 100; display: flex;"> <div style="flex-grow: 100; display: flex;">
<button class="btn btn-xs" id="jump-to-note-button" title="CTRL+J">Jump to note</button> <button class="btn btn-xs" id="jump-to-note-dialog-button" title="CTRL+J">Jump to note</button>
<button class="btn btn-xs" id="recent-notes-button" title="CTRL+E">Recent notes</button> <button class="btn btn-xs" id="recent-notes-button" title="CTRL+E">Recent notes</button>
<button class="btn btn-xs" id="recent-changes-button">Recent changes</button> <button class="btn btn-xs" id="recent-changes-button">Recent changes</button>
<div> <div>
@@ -57,18 +51,18 @@
</div> </div>
<div style="grid-area: left-pane; display: flex; flex-direction: column;" class="hide-toggle"> <div style="grid-area: left-pane; display: flex; flex-direction: column;" class="hide-toggle">
<div style="display: flex; justify-content: space-around; padding: 10px 0 10px 0; margin: 0 20px 0 20px; border: 1px solid #ccc;"> <div style="display: flex; justify-content: space-around; padding: 10px 0 10px 0; margin: 0 10px 0 16px; border: 1px solid #ccc;">
<a id="create-top-level-note-button" title="Create new top level note" class="icon-action" <a id="create-top-level-note-button" title="Create new top level note" class="icon-action"
style="background: url('/images/icons/file-plus.png')"></a> style="background: url('/images/icons/file-plus-24.png')"></a>
<a id="collapse-tree-button" title="Collapse note tree" class="icon-action" <a id="collapse-tree-button" title="Collapse note tree" class="icon-action"
style="background: url('/images/icons/list.png')"></a> style="background: url('/images/icons/list-24.png')"></a>
<a id="scroll-to-current-note-button" title="Scroll to current note. Shortcut CTRL+." class="icon-action" <a id="scroll-to-current-note-button" title="Scroll to current note. Shortcut CTRL+." class="icon-action"
style="background: url('/images/icons/crosshair.png')"></a> style="background: url('/images/icons/crosshair-24.png')"></a>
<a id="toggle-search-button" title="Search in notes" class="icon-action" <a id="toggle-search-button" title="Search in notes" class="icon-action"
style="background: url('/images/icons/search.png')"></a> style="background: url('/images/icons/search-24.png')"></a>
</div> </div>
<input type="file" id="import-upload" style="display: none" /> <input type="file" id="import-upload" style="display: none" />
@@ -76,22 +70,37 @@
<div id="search-box" style="display: none; padding: 10px; margin-top: 10px;"> <div id="search-box" style="display: none; padding: 10px; margin-top: 10px;">
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<input name="search-text" placeholder="Search text, labels" style="flex-grow: 100; margin-left: 5px; margin-right: 5px;" autocomplete="off"> <input name="search-text" placeholder="Search text, labels" style="flex-grow: 100; margin-left: 5px; margin-right: 5px;" autocomplete="off">
<button id="do-search-button" class="btn btn-primary btn-sm" title="Search">Search</button> <button id="do-search-button" class="btn btn-sm icon-button" title="Search (enter)"
style="background-image: url('/images/icons/search-20.png');"></button>
&nbsp;
<button id="save-search-button" class="btn btn-sm icon-button" title="Save search"
style="background-image: url('/images/icons/save-20.png');"></button>
&nbsp;
<button id="close-search-button" class="btn btn-sm icon-button" title="Close search"
style="background-image: url('/images/icons/x-20.png');"></button>
</div>
</div> </div>
<div style="display: flex; align-items: center; justify-content: space-evenly; margin-top: 10px;"> <div id="search-results">
<button id="reset-search-button" class="btn btn-sm" title="Reset search">Reset search</button> <strong>Search results:</strong>
<button id="save-search-button" class="btn btn-sm" title="Save search">Save search</button> <ul id="search-results-inner">
</div> <li>aaa</li>
<li>bbb</li>
<li>ccc</li>
</ul>
</div> </div>
<div id="tree"></div> <div id="tree"></div>
</div> </div>
<div style="grid-area: title;"> <div style="grid-area: title;">
<div class="hide-toggle" style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<div class="dropdown"> <div class="dropdown hide-toggle">
<button id="note-path-list-button" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle"> <button id="note-path-list-button" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle">
<span id="note-path-count">1 path</span> <span id="note-path-count">1 path</span>
@@ -103,32 +112,40 @@
<input autocomplete="off" value="" id="note-title" style="margin-left: 15px; font-size: x-large; border: 0; flex-grow: 100;" tabindex="1"> <input autocomplete="off" value="" id="note-title" style="margin-left: 15px; font-size: x-large; border: 0; flex-grow: 100;" tabindex="1">
<div class="hide-toggle" style="display: flex; align-items: center;">
<span id="note-id-display" title="Note ID"></span> <span id="note-id-display" title="Note ID"></span>
<a title="Protect the note so that password will be required to view the note" <button class="btn btn-sm icon-button"
class="icon-action" style="display: none; margin-right: 10px; background-image: url('/images/icons/edit-20.png');"
id="protect-button" title="Toggle edit"
style="display: none; background: url('images/icons/lock.png')"></a> id="toggle-edit-button"></button>
<a title="Unprotect note so that password will not be required to access this note in the future" <button class="btn btn-sm icon-button"
class="icon-action" style="display: none; margin-right: 10px; background-image: url('/images/icons/play-20.png');"
title="Render (Ctrl+Enter)"
id="render-button"></button>
<button class="btn btn-sm icon-button"
style="display: none; margin-right: 10px; background-image: url('/images/icons/play-20.png');"
title="Execute (Ctrl+Enter)"
id="execute-script-button"></button>
<div>
<button type="button"
class="btn btn-sm icon-button"
id="protect-button"
title="Protected note can be viewed and edited only after entering password"
style="background-image: url('/images/icons/shield-20.png');">
</button><button type="button"
class="btn btn-sm icon-button"
id="unprotect-button" id="unprotect-button"
style="display: none; background: url('images/icons/unlock.png')"></a> title="Not protected note can be viewed without entering password"
style="background-image: url('/images/icons/shield-off-20.png');">
</button>
</div>
&nbsp; &nbsp; &nbsp; &nbsp;
<button class="btn btn-sm"
style="display: none; margin-right: 10px"
id="toggle-edit-button">Toggle edit</button>
<button class="btn btn-sm"
style="display: none; margin-right: 10px"
id="render-button">Render <kbd>Ctrl+Enter</kbd></button>
<button class="btn btn-sm"
style="display: none; margin-right: 10px"
id="execute-script-button">Execute <kbd>Ctrl+Enter</kbd></button>
<div class="dropdown" id="note-type" data-bind="visible: type() != 'search'"> <div class="dropdown" id="note-type" data-bind="visible: type() != 'search'">
<button data-bind="disable: isDisabled()" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm"> <button data-bind="disable: isDisabled()" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm">
Type: <span data-bind="text: typeString()"></span> Type: <span data-bind="text: typeString()"></span>
@@ -146,7 +163,7 @@
</ul> </ul>
</div> </div>
<div class="dropdown" style="margin-left: 10px; margin-right: 10px;"> <div class="dropdown" id="note-actions">
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm"> <button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm">
Note actions Note actions
<span class="caret"></span> <span class="caret"></span>
@@ -160,6 +177,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div id="note-detail-wrapper"> <div id="note-detail-wrapper">
<div id="note-detail-component-wrapper"> <div id="note-detail-component-wrapper">
@@ -248,7 +266,7 @@
<input id="recent-notes-search-input" class="form-control"/> <input id="recent-notes-search-input" class="form-control"/>
</div> </div>
<div id="add-link-dialog" title="Add link" style="display: none;"> <div id="add-link-dialog" title="Add note link" style="display: none;">
<form id="add-link-form"> <form id="add-link-form">
<div id="add-link-type-div" class="radio"> <div id="add-link-type-div" class="radio">
<label title="Add HTML link to the selected note at cursor in current note"> <label title="Add HTML link to the selected note at cursor in current note">
@@ -266,7 +284,7 @@
<div class="form-group"> <div class="form-group">
<label for="note-autocomplete">Note</label> <label for="note-autocomplete">Note</label>
<input id="note-autocomplete" style="width: 100%;"> <input id="note-autocomplete" placeholder="search for note by its name" style="width: 100%;">
</div> </div>
<div class="form-group" id="add-link-title-form-group"> <div class="form-group" id="add-link-title-form-group">
@@ -279,7 +297,7 @@
<input id="clone-prefix" style="width: 100%;"> <input id="clone-prefix" style="width: 100%;">
</div> </div>
<button class="btn btn-sm">Add link</button> <button class="btn btn-sm">Add note link</button>
</form> </form>
</div> </div>
@@ -287,10 +305,14 @@
<form id="jump-to-note-form"> <form id="jump-to-note-form">
<div class="form-group"> <div class="form-group">
<label for="jump-to-note-autocomplete">Note</label> <label for="jump-to-note-autocomplete">Note</label>
<input id="jump-to-note-autocomplete" style="width: 100%;"> <input id="jump-to-note-autocomplete" placeholder="search for note by its name" style="width: 100%;">
</div> </div>
<button name="action" value="jump" class="btn btn-sm">Jump <kbd>enter</kbd></button> <div style="display: flex; justify-content: space-between;">
<button id="jump-to-note-button" class="btn btn-sm btn-primary">Jump <kbd>enter</kbd></button>
<button id="show-in-full-text-button" class="btn btn-sm">Search in full text <kbd>ctrl+enter</kbd></button>
</div>
</form> </form>
</div> </div>
@@ -308,12 +330,35 @@
<div id="options-dialog" title="Options" style="display: none;"> <div id="options-dialog" title="Options" style="display: none;">
<div id="options-tabs"> <div id="options-tabs">
<ul> <ul>
<li><a href="#appearance">Apperance</a></li>
<li><a href="#change-password">Change password</a></li> <li><a href="#change-password">Change password</a></li>
<li><a href="#protected-session-timeout">Protected session</a></li> <li><a href="#protected-session-timeout">Protected session</a></li>
<li><a href="#note-revision-snapshot-time-interval">Note revisions</a></li> <li><a href="#note-revision-snapshot-time-interval">Note revisions</a></li>
<li><a href="#advanced">Advanced</a></li> <li><a href="#advanced">Advanced</a></li>
<li><a href="#about">About Trilium</a></li> <li><a href="#about">About Trilium</a></li>
</ul> </ul>
<div id="appearance">
<p>Settings on this options tab are saved automatically after each change.</p>
<form>
<div class="form-group">
<label for="theme-select">Theme</label>
<select class="form-control" id="theme-select">
<option value="white">White</option>
<option value="dark">Dark</option>
<option value="black">Black</option>
</select>
</div>
<div class="form-group">
<label for="zoom-factor-select">Zoom factor (desktop build only)</label>
<input type="number" class="form-control" id="zoom-factor-select" min="0.3" max="2.0" step="0.1"/>
</div>
<p>Zooming can be controlled with CTRL-+ and CTRL-= shortcuts as well.</p>
</form>
</div>
<div id="change-password"> <div id="change-password">
<form id="change-password-form"> <form id="change-password-form">
<div class="form-group"> <div class="form-group">
@@ -402,12 +447,14 @@
<th>App version:</th> <th>App version:</th>
<td id="app-version"></td> <td id="app-version"></td>
</tr> </tr>
<tr> <tr>
<th>DB version:</th> <th>DB version:</th>
<td id="db-version"></td> <td id="db-version"></td>
</tr> </tr>
<tr>
<th>Sync version:</th>
<td id="sync-version"></td>
</tr>
<tr> <tr>
<th>Build date:</th> <th>Build date:</th>
<td id="build-date"></td> <td id="build-date"></td>

View File

@@ -1,72 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Migration</title>
</head>
<body>
<div style="width: 800px; margin: auto;">
<h1>Migration</h1>
<div id="up-to-date" style="display:none;">
<p>Your database is up-to-date with the application.</p>
<a href="/" class="btn btn-success">Continue to app</a>
</div>
<div id="need-to-migrate" style="display:none;">
<p>Your database needs to be migrated to new version before you can use the application again.
Database will be backed up before migration in case of something going wrong.</p>
<table class="table table-bordered" style="width: 200px;">
<tr>
<th>Application version:</th>
<td id="app-db-version" style="text-align: right;"></td>
<tr>
<th>Database version:</th>
<td id="db-version" style="text-align: right;"></td>
</tr>
</table>
<button class="btn btn-warning" id="run-migration">Run migration</button>
</div>
<div id="migration-result" style="display:none;">
<h2>Migration result</h2>
<table id="migration-table" class="table">
<tr>
<th>Database version</th>
<th>Name</th>
<th>Success</th>
<th>Error</th>
</tr>
</table>
<a href="/" class="btn btn-success">Continue to app</a>
</div>
</div>
<script type="text/javascript">
const baseApiUrl = 'api/';
</script>
<!-- Required for correct loading of scripts in Electron -->
<script>
if (typeof module === 'object') {
window.module = module; module = undefined;
}
const glob = {
sourceId: ''
};
</script>
<script src="libraries/jquery.min.js"></script>
<link href="libraries/bootstrap/css/bootstrap.css" rel="stylesheet">
<script src="libraries/bootstrap/js/bootstrap.js"></script>
<script src="javascripts/migration.js" type="module"></script>
</body>
</html>

View File

@@ -18,6 +18,7 @@ const log = require('./services/log');
const appInfo = require('./services/app_info'); const appInfo = require('./services/app_info');
const messagingService = require('./services/messaging'); const messagingService = require('./services/messaging');
const utils = require('./services/utils'); const utils = require('./services/utils');
const sqlInit = require('./services/sql_init.js');
const port = normalizePort(config['Network']['port'] || '3000'); const port = normalizePort(config['Network']['port'] || '3000');
app.set('port', port); app.set('port', port);
@@ -54,7 +55,7 @@ httpServer.listen(port);
httpServer.on('error', onError); httpServer.on('error', onError);
httpServer.on('listening', onListening); httpServer.on('listening', onListening);
messagingService.init(httpServer, sessionParser); sqlInit.dbReady.then(() => messagingService.init(httpServer, sessionParser));
if (utils.isElectron()) { if (utils.isElectron()) {
const electronRouting = require('./routes/electron'); const electronRouting = require('./routes/electron');