Compare commits

...

20 Commits

Author SHA1 Message Date
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
azivner
c07785be67 release 0.14.0 2018-05-31 23:23:44 -04:00
azivner
80d2457b23 moved parent list next to note title 2018-05-31 23:21:47 -04:00
azivner
5dde2752d2 add switch to manually enter/leave protected session, fixes #107 2018-05-31 20:00:39 -04:00
azivner
8bf4633cd0 fixes 2018-05-30 23:18:56 -04:00
azivner
bd66b8a1c8 fix issue with limitation of number of SQLite parameters (999) which caused problems when loading tree which was too expanded 2018-05-30 20:28:10 -04:00
azivner
be51e533fc OPML import support (issue #78) 2018-05-29 20:32:13 -04:00
azivner
f47ae12019 OPML export support (issue #78), import missing for now 2018-05-27 12:26:34 -04:00
azivner
cab54a458f unifying surrogate keys for event log and options, fixes #103 2018-05-26 23:25:09 -04:00
azivner
a30734f1bc Add history backwards/forwards buttons, fixes #94 2018-05-26 22:54:06 -04:00
azivner
7ad9f7b129 fixed layouting issues 2018-05-26 19:58:08 -04:00
azivner
40a32e6826 render notes can be edited and can contain HTML markup 2018-05-26 19:27:47 -04:00
azivner
ab0486aaf1 expose root node, fixes #101 2018-05-26 16:16:34 -04:00
azivner
874593a167 fix code editor growing 2018-05-26 15:28:36 -04:00
azivner
03bf33630e unify audit fields, fixes #102 2018-05-26 12:38:25 -04:00
azivner
933cce1b94 fix hideInAutocomplete bug 2018-05-26 10:50:13 -04:00
azivner
4a6ff573f8 fixed autocomplete issues with capitalization 2018-05-26 10:24:33 -04:00
azivner
1a737f7d19 expose add link on UI, fixes #95 2018-05-26 10:04:40 -04:00
51 changed files with 834 additions and 438 deletions

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<dataSource name="document.db"> <dataSource name="document.db">
<database-model serializer="dbm" rdbms="SQLITE" format-version="4.8"> <database-model serializer="dbm" rdbms="SQLITE" format-version="4.9">
<root id="1"> <root id="1">
<ServerVersion>3.16.1</ServerVersion> <ServerVersion>3.16.1</ServerVersion>
</root> </root>
@@ -50,546 +50,616 @@
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression> <DefaultExpression>0</DefaultExpression>
</column> </column>
<index id="24" parent="6" name="sqlite_autoindex_api_tokens_1"> <column id="24" parent="6" name="hash">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="25" parent="6" name="sqlite_autoindex_api_tokens_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>apiTokenId</ColNames> <ColNames>apiTokenId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<key id="25" parent="6"> <key id="26" parent="6">
<ColNames>apiTokenId</ColNames> <ColNames>apiTokenId</ColNames>
<Primary>1</Primary> <Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_api_tokens_1</UnderlyingIndexName> <UnderlyingIndexName>sqlite_autoindex_api_tokens_1</UnderlyingIndexName>
</key> </key>
<column id="26" parent="7" name="branchId"> <column id="27" parent="7" name="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="27" parent="7" name="noteId"> <column id="28" parent="7" name="noteId">
<Position>2</Position> <Position>2</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="28" parent="7" name="parentNoteId"> <column id="29" parent="7" name="parentNoteId">
<Position>3</Position> <Position>3</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="29" parent="7" name="notePosition"> <column id="30" parent="7" name="notePosition">
<Position>4</Position> <Position>4</Position>
<DataType>INTEGER|0s</DataType> <DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="30" parent="7" name="prefix"> <column id="31" parent="7" name="prefix">
<Position>5</Position> <Position>5</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
</column> </column>
<column id="31" parent="7" name="isExpanded"> <column id="32" parent="7" name="isExpanded">
<Position>6</Position> <Position>6</Position>
<DataType>BOOLEAN|0s</DataType> <DataType>BOOLEAN|0s</DataType>
</column> </column>
<column id="32" parent="7" name="isDeleted"> <column id="33" parent="7" name="isDeleted">
<Position>7</Position> <Position>7</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="33" parent="7" name="dateModified"> <column id="34" parent="7" name="dateModified">
<Position>8</Position> <Position>8</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<index id="34" parent="7" name="sqlite_autoindex_branches_1"> <column id="35" parent="7" name="hash">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="36" parent="7" name="dateCreated">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;1970-01-01T00:00:00.000Z&apos;</DefaultExpression>
</column>
<index id="37" parent="7" name="sqlite_autoindex_branches_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>
<index id="35" parent="7" name="IDX_branches_noteId_parentNoteId"> <index id="38" parent="7" name="IDX_branches_noteId_parentNoteId">
<ColNames>noteId <ColNames>noteId
parentNoteId</ColNames> parentNoteId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<index id="36" parent="7" name="IDX_branches_noteId"> <index id="39" parent="7" name="IDX_branches_noteId">
<ColNames>noteId</ColNames> <ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<key id="37" parent="7"> <index id="40" parent="7" name="IDX_branches_parentNoteId">
<ColNames>parentNoteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="41" parent="7">
<ColNames>branchId</ColNames> <ColNames>branchId</ColNames>
<Primary>1</Primary> <Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName> <UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName>
</key> </key>
<column id="38" parent="8" name="id"> <column id="42" parent="8" 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="39" parent="8" name="noteId"> <column id="43" parent="8" name="noteId">
<Position>2</Position> <Position>2</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
</column> </column>
<column id="40" parent="8" name="comment"> <column id="44" parent="8" name="comment">
<Position>3</Position> <Position>3</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
</column> </column>
<column id="41" parent="8" name="dateAdded"> <column id="45" parent="8" name="dateCreated">
<Position>4</Position> <Position>4</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<key id="42" parent="8"> <key id="46" parent="8">
<ColNames>id</ColNames> <ColNames>id</ColNames>
<Primary>1</Primary> <Primary>1</Primary>
</key> </key>
<column id="43" parent="9" name="imageId"> <column id="47" 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="44" parent="9" name="format"> <column id="48" 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="45" parent="9" name="checksum"> <column id="49" 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="46" parent="9" name="name"> <column id="50" 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="47" parent="9" name="data"> <column id="51" parent="9" name="data">
<Position>5</Position> <Position>5</Position>
<DataType>BLOB|0s</DataType> <DataType>BLOB|0s</DataType>
</column> </column>
<column id="48" parent="9" name="isDeleted"> <column id="52" 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="49" parent="9" name="dateModified"> <column id="53" 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="50" parent="9" name="dateCreated"> <column id="54" parent="9" name="dateCreated">
<Position>8</Position> <Position>8</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<index id="51" parent="9" name="sqlite_autoindex_images_1"> <column id="55" parent="9" name="hash">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="56" 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="52" parent="9"> <key id="57" 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="53" parent="10" name="labelId"> <column id="58" 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="54" parent="10" name="noteId"> <column id="59" 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="55" parent="10" name="name"> <column id="60" 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="56" parent="10" name="value"> <column id="61" 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="57" parent="10" name="position"> <column id="62" 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="58" parent="10" name="dateCreated"> <column id="63" 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="59" parent="10" name="dateModified"> <column id="64" 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="60" parent="10" name="isDeleted"> <column id="65" parent="10" name="isDeleted">
<Position>8</Position> <Position>8</Position>
<DataType>INT|0s</DataType> <DataType>INT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<index id="61" parent="10" name="sqlite_autoindex_labels_1"> <column id="66" parent="10" name="hash">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="67" 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="62" parent="10" name="IDX_labels_noteId"> <index id="68" parent="10" name="IDX_labels_noteId">
<ColNames>noteId</ColNames> <ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<index id="63" parent="10" name="IDX_labels_name_value"> <index id="69" parent="10" name="IDX_labels_name_value">
<ColNames>name <ColNames>name
value</ColNames> value</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<key id="64" parent="10"> <key id="70" 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="65" parent="11" name="noteImageId"> <column id="71" 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="66" parent="11" name="noteId"> <column id="72" 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="67" parent="11" name="imageId"> <column id="73" 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="68" parent="11" name="isDeleted"> <column id="74" 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="69" parent="11" name="dateModified"> <column id="75" 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="70" parent="11" name="dateCreated"> <column id="76" parent="11" name="dateCreated">
<Position>6</Position> <Position>6</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<index id="71" parent="11" name="sqlite_autoindex_note_images_1"> <column id="77" parent="11" name="hash">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="78" 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="72" parent="11" name="IDX_note_images_noteId_imageId"> <index id="79" parent="11" name="IDX_note_images_noteId_imageId">
<ColNames>noteId <ColNames>noteId
imageId</ColNames> imageId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<index id="73" parent="11" name="IDX_note_images_noteId"> <index id="80" parent="11" name="IDX_note_images_noteId">
<ColNames>noteId</ColNames> <ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<index id="74" parent="11" name="IDX_note_images_imageId"> <index id="81" parent="11" name="IDX_note_images_imageId">
<ColNames>imageId</ColNames> <ColNames>imageId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<key id="75" parent="11"> <key id="82" 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="76" parent="12" name="noteRevisionId"> <column id="83" 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="77" parent="12" name="noteId"> <column id="84" 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="78" parent="12" name="title"> <column id="85" parent="12" name="title">
<Position>3</Position> <Position>3</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
</column> </column>
<column id="79" parent="12" name="content"> <column id="86" parent="12" name="content">
<Position>4</Position> <Position>4</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
</column> </column>
<column id="80" parent="12" name="isProtected"> <column id="87" 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="81" parent="12" name="dateModifiedFrom"> <column id="88" 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="82" parent="12" name="dateModifiedTo"> <column id="89" 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="83" parent="12" name="type"> <column id="90" 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="84" parent="12" name="mime"> <column id="91" 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>
<index id="85" parent="12" name="sqlite_autoindex_note_revisions_1"> <column id="92" parent="12" name="hash">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="93" 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="86" parent="12" name="IDX_note_revisions_noteId"> <index id="94" parent="12" name="IDX_note_revisions_noteId">
<ColNames>noteId</ColNames> <ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<index id="87" parent="12" name="IDX_note_revisions_dateModifiedFrom"> <index id="95" parent="12" name="IDX_note_revisions_dateModifiedFrom">
<ColNames>dateModifiedFrom</ColNames> <ColNames>dateModifiedFrom</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<index id="88" parent="12" name="IDX_note_revisions_dateModifiedTo"> <index id="96" parent="12" name="IDX_note_revisions_dateModifiedTo">
<ColNames>dateModifiedTo</ColNames> <ColNames>dateModifiedTo</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<key id="89" parent="12"> <key id="97" 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="90" parent="13" name="noteId"> <column id="98" 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="91" parent="13" name="title"> <column id="99" 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="92" parent="13" name="content"> <column id="100" 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="93" parent="13" name="isProtected"> <column id="101" 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="94" parent="13" name="isDeleted"> <column id="102" 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="95" parent="13" name="dateCreated"> <column id="103" 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="96" parent="13" name="dateModified"> <column id="104" 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="97" parent="13" name="type"> <column id="105" 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="98" parent="13" name="mime"> <column id="106" 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>
<index id="99" parent="13" name="sqlite_autoindex_notes_1"> <column id="107" parent="13" name="hash">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="108" 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="100" parent="13" name="IDX_notes_isDeleted"> <index id="109" parent="13" name="IDX_notes_type">
<ColNames>isDeleted</ColNames> <ColNames>type</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<key id="101" parent="13"> <key id="110" 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="102" parent="14" name="name"> <column id="111" parent="14" name="name">
<Position>1</Position> <Position>1</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="103" parent="14" name="value"> <column id="112" parent="14" name="value">
<Position>2</Position> <Position>2</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
</column> </column>
<column id="104" parent="14" name="dateModified"> <column id="113" parent="14" name="dateModified">
<Position>3</Position> <Position>3</Position>
<DataType>INT|0s</DataType> <DataType>INT|0s</DataType>
</column> </column>
<column id="105" parent="14" name="isSynced"> <column id="114" parent="14" name="isSynced">
<Position>4</Position> <Position>4</Position>
<DataType>INTEGER|0s</DataType> <DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression> <DefaultExpression>0</DefaultExpression>
</column> </column>
<index id="106" parent="14" name="sqlite_autoindex_options_1"> <column id="115" parent="14" name="hash">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="116" parent="14" name="dateCreated">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;1970-01-01T00:00:00.000Z&apos;</DefaultExpression>
</column>
<index id="117" parent="14" name="sqlite_autoindex_options_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>name</ColNames> <ColNames>name</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<key id="107" parent="14"> <key id="118" parent="14">
<ColNames>name</ColNames> <ColNames>name</ColNames>
<Primary>1</Primary> <Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName> <UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName>
</key> </key>
<column id="108" parent="15" name="branchId"> <column id="119" 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="109" parent="15" name="notePath"> <column id="120" 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="110" parent="15" name="dateAccessed"> <column id="121" parent="15" name="dateCreated">
<Position>3</Position> <Position>3</Position>
<DataType>TEXT|0s</DataType> <DataType>TEXT|0s</DataType>
<NotNull>1</NotNull> <NotNull>1</NotNull>
</column> </column>
<column id="111" parent="15" name="isDeleted"> <column id="122" parent="15" name="isDeleted">
<Position>4</Position> <Position>4</Position>
<DataType>INT|0s</DataType> <DataType>INT|0s</DataType>
</column> </column>
<index id="112" parent="15" name="sqlite_autoindex_recent_notes_1"> <column id="123" parent="15" name="hash">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="124" 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="113" parent="15"> <key id="125" 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="114" parent="16" name="sourceId"> <column id="126" 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="115" parent="16" name="dateCreated"> <column id="127" 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="116" parent="16" name="sqlite_autoindex_source_ids_1"> <index id="128" 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="117" parent="16"> <key id="129" 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="118" parent="17" name="type"> <column id="130" parent="17" name="type">
<Position>1</Position> <Position>1</Position>
<DataType>text|0s</DataType> <DataType>text|0s</DataType>
</column> </column>
<column id="119" parent="17" name="name"> <column id="131" parent="17" name="name">
<Position>2</Position> <Position>2</Position>
<DataType>text|0s</DataType> <DataType>text|0s</DataType>
</column> </column>
<column id="120" parent="17" name="tbl_name"> <column id="132" parent="17" name="tbl_name">
<Position>3</Position> <Position>3</Position>
<DataType>text|0s</DataType> <DataType>text|0s</DataType>
</column> </column>
<column id="121" parent="17" name="rootpage"> <column id="133" parent="17" name="rootpage">
<Position>4</Position> <Position>4</Position>
<DataType>integer|0s</DataType> <DataType>integer|0s</DataType>
</column> </column>
<column id="122" parent="17" name="sql"> <column id="134" parent="17" name="sql">
<Position>5</Position> <Position>5</Position>
<DataType>text|0s</DataType> <DataType>text|0s</DataType>
</column> </column>
<column id="123" parent="18" name="name"> <column id="135" parent="18" name="name">
<Position>1</Position> <Position>1</Position>
</column> </column>
<column id="124" parent="18" name="seq"> <column id="136" parent="18" name="seq">
<Position>2</Position> <Position>2</Position>
</column> </column>
<column id="125" parent="19" name="id"> <column id="137" 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="126" parent="19" name="entityName"> <column id="138" 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="127" parent="19" name="entityId"> <column id="139" 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="128" parent="19" name="sourceId"> <column id="140" 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="129" parent="19" name="syncDate"> <column id="141" 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="130" parent="19" name="IDX_sync_entityName_entityId"> <index id="142" 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="131" parent="19" name="IDX_sync_syncDate"> <index id="143" parent="19" name="IDX_sync_syncDate">
<ColNames>syncDate</ColNames> <ColNames>syncDate</ColNames>
<ColumnCollations></ColumnCollations> <ColumnCollations></ColumnCollations>
</index> </index>
<key id="132" parent="19"> <key id="144" parent="19">
<ColNames>id</ColNames> <ColNames>id</ColNames>
<Primary>1</Primary> <Primary>1</Primary>
</key> </key>

View File

@@ -0,0 +1,30 @@
ALTER TABLE branches ADD dateCreated TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z';
CREATE TABLE `event_log_mig` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`noteId` TEXT,
`comment` TEXT,
`dateCreated` TEXT NOT NULL
);
INSERT INTO event_log_mig (id, noteId, comment, dateCreated)
SELECT id, noteId, comment, dateAdded FROM event_log;
DROP TABLE event_log;
ALTER TABLE event_log_mig RENAME TO event_log;
ALTER TABLE options ADD dateCreated TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z';
CREATE TABLE `recent_notes_mig` (
`branchId` TEXT NOT NULL PRIMARY KEY,
`notePath` TEXT NOT NULL,
hash TEXT DEFAULT "" NOT NULL,
`dateCreated` TEXT NOT NULL,
isDeleted INT
);
INSERT INTO recent_notes_mig (branchId, notePath, hash, dateCreated, isDeleted)
SELECT branchId, notePath, hash, dateAccessed, isDeleted FROM recent_notes;
DROP TABLE recent_notes;
ALTER TABLE recent_notes_mig RENAME TO recent_notes;

View File

@@ -0,0 +1 @@
UPDATE notes SET mime = 'text/html' WHERE type = 'render';

View File

@@ -0,0 +1,29 @@
CREATE TABLE `event_log_mig` (
`eventId` TEXT NOT NULL PRIMARY KEY,
`noteId` TEXT,
`comment` TEXT,
`dateCreated` TEXT NOT NULL
);
INSERT INTO event_log_mig (eventId, noteId, comment, dateCreated)
SELECT id, noteId, comment, dateCreated FROM event_log;
DROP TABLE event_log;
ALTER TABLE event_log_mig RENAME TO event_log;
create table options_mig
(
optionId TEXT NOT NULL PRIMARY KEY,
name TEXT not null,
value TEXT,
dateModified INT,
isSynced INTEGER default 0 not null,
hash TEXT default "" not null,
dateCreated TEXT default '1970-01-01T00:00:00.000Z' not null
);
INSERT INTO options_mig (optionId, name, value, dateModified, isSynced, hash, dateCreated)
SELECT name || "_key", name, value, dateModified, isSynced, hash, dateCreated FROM options;
DROP TABLE options;
ALTER TABLE options_mig RENAME TO options;

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,
`dateCreated` TEXT NOT NULL,
isDeleted INT
, hash TEXT DEFAULT "" NOT NULL);
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"
(
optionId TEXT NOT NULL PRIMARY KEY,
name TEXT not null,
value TEXT,
dateModified INT,
isSynced INTEGER default 0 not null,
hash TEXT default "" not null,
dateCreated TEXT default '1970-01-01T00:00:00.000Z' not null
); );

8
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "trilium", "name": "trilium",
"version": "0.12.0", "version": "0.13.0-beta",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -13618,9 +13618,9 @@
}, },
"dependencies": { "dependencies": {
"xmlbuilder": { "xmlbuilder": {
"version": "9.0.4", "version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-UZy0ymhtAFqEINNJbz8MruzKWA8=" "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
} }
} }
}, },

View File

@@ -1,7 +1,7 @@
{ {
"name": "trilium", "name": "trilium",
"description": "Trilium Notes", "description": "Trilium Notes",
"version": "0.13.0-beta", "version": "0.14.1",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"main": "electron.js", "main": "electron.js",
"repository": { "repository": {
@@ -59,7 +59,8 @@
"sqlite": "^2.9.2", "sqlite": "^2.9.2",
"tar-stream": "^1.6.1", "tar-stream": "^1.6.1",
"unescape": "^1.0.1", "unescape": "^1.0.1",
"ws": "^5.2.0" "ws": "^5.2.0",
"xml2js": "^0.4.19"
}, },
"devDependencies": { "devDependencies": {
"electron": "^2.0.1", "electron": "^2.0.1",

View File

@@ -27,7 +27,11 @@ class Branch extends Entity {
this.isDeleted = false; this.isDeleted = false;
} }
this.dateModified = dateUtils.nowDate() if (!this.dateCreated) {
this.dateCreated = dateUtils.nowDate();
}
this.dateModified = dateUtils.nowDate();
} }
} }

View File

@@ -40,7 +40,7 @@ class Note extends Entity {
} }
isHtml() { isHtml() {
return (this.type === "code" || this.type === "file") && this.mime === "text/html"; return (this.type === "code" || this.type === "file" || this.type === "render") && this.mime === "text/html";
} }
getScriptEnv() { getScriptEnv() {

View File

@@ -5,8 +5,8 @@ const dateUtils = require('../services/date_utils');
class Option extends Entity { class Option extends Entity {
static get tableName() { return "options"; } static get tableName() { return "options"; }
static get primaryKeyName() { return "name"; } static get primaryKeyName() { return "optionId"; }
static get hashedProperties() { return ["name", "value"]; } static get hashedProperties() { return ["optionId", "name", "value"]; }
beforeSaving() { beforeSaving() {
super.beforeSaving(); super.beforeSaving();

View File

@@ -1,11 +1,24 @@
"use strict"; "use strict";
const Entity = require('./entity'); const Entity = require('./entity');
const dateUtils = require('../services/date_utils');
class RecentNote extends Entity { class RecentNote extends Entity {
static get tableName() { return "recent_notes"; } static get tableName() { return "recent_notes"; }
static get primaryKeyName() { return "branchId"; } static get primaryKeyName() { return "branchId"; }
static get hashedProperties() { return ["branchId", "notePath", "dateAccessed", "isDeleted"]; } static get hashedProperties() { return ["branchId", "notePath", "dateCreated", "isDeleted"]; }
beforeSaving() {
super.beforeSaving();
if (!this.isDeleted) {
this.isDeleted = false;
}
if (!this.dateCreated) {
this.dateCreated = dateUtils.nowDate();
}
}
} }
module.exports = RecentNote; module.exports = RecentNote;

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path d="M63.966,45.043c0.008-0.009,0.021-0.021,0.027-0.029c0.938-1.156-0.823-13.453-5.063-20.125 c-1.389-2.186-2.239-3.423-3.219-4.719c-3.907-5.166-6-6.125-6-6.125S35.732,24.78,36.149,44.315 c-1.754,0.065-11.218,7.528-14.826,14.388c-1.206,2.291-1.856,3.645-2.493,5.141c-2.539,5.957-2.33,8.25-2.33,8.25 s16.271,6.79,33.014-3.294c0.007,0.021,0.013,0.046,0.02,0.063c0.537,1.389,12.08,5.979,19.976,5.621 c2.587-0.116,4.084-0.238,5.696-0.444c6.424-0.818,8.298-2.157,8.298-2.157S81.144,54.396,63.966,45.043z M50.787,65.343 c1.059-1.183,4.648-5.853,0.995-11.315c-0.253-0.377-0.496-0.236-0.496-0.236s0.063,10.822-5.162,12.359 c-5.225,1.537-13.886,4.4-20.427,0.455C25,66.186,26.924,53.606,38.544,47.229c0.546,1.599,2.836,6.854,9.292,6.409 c0.453-0.031,0.453-0.313,0.453-0.313s-9.422-5.328-8.156-10.625s3.089-14.236,9.766-17.948c0.714-0.397,10.746,7.593,10.417,20.94 c-1.606-0.319-7.377-1.004-10.226,4.864c-0.198,0.409,0.046,0.549,0.046,0.549s9.31-5.521,13.275-1.789 c3.965,3.733,10.813,9.763,10.71,17.4C74.111,67.533,62.197,72.258,50.787,65.343z M35.613,35.145c0,0-0.991,3.241-0.603,7.524 l-13.393-7.524C21.618,35.145,27.838,30.931,35.613,35.145z M21.193,36.03l13.344,7.612c-3.872,1.872-6.142,4.388-6.142,4.388 C20.78,43.531,21.193,36.03,21.193,36.03z M72.287,49.064c0,0-2.321-2.471-6.23-4.263l13.187-7.881 C79.243,36.92,79.808,44.413,72.287,49.064z M78.687,36.113l-13.237,7.794c0.3-4.291-0.754-7.511-0.754-7.511 C72.383,32.025,78.687,36.113,78.687,36.113z M42.076,73.778c0,0,3.309-0.737,6.845-3.185l0.056,15.361 C48.977,85.955,42.244,82.621,42.076,73.778z M49.956,85.888L50,70.526c3.539,2.445,6.846,3.181,6.846,3.181 C56.686,82.551,49.956,85.888,49.956,85.888z"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -2,7 +2,8 @@ import cloningService from '../services/cloning.js';
import linkService from '../services/link.js'; import linkService from '../services/link.js';
import noteDetailService from '../services/note_detail.js'; import noteDetailService from '../services/note_detail.js';
import treeUtils from '../services/tree_utils.js'; import treeUtils from '../services/tree_utils.js';
import autocompleteService from '../services/autocomplete.js'; import server from "../services/server.js";
import noteDetailText from "../services/note_detail_text.js";
const $dialog = $("#add-link-dialog"); const $dialog = $("#add-link-dialog");
const $form = $("#add-link-form"); const $form = $("#add-link-form");
@@ -11,6 +12,7 @@ const $linkTitle = $("#link-title");
const $clonePrefix = $("#clone-prefix"); const $clonePrefix = $("#clone-prefix");
const $linkTitleFormGroup = $("#add-link-title-form-group"); const $linkTitleFormGroup = $("#add-link-title-form-group");
const $prefixFormGroup = $("#add-link-prefix-form-group"); const $prefixFormGroup = $("#add-link-prefix-form-group");
const $linkTypeDiv = $("#add-link-type-div");
const $linkTypes = $("input[name='add-link-type']"); const $linkTypes = $("input[name='add-link-type']");
const $linkTypeHtml = $linkTypes.filter('input[value="html"]'); const $linkTypeHtml = $linkTypes.filter('input[value="html"]');
@@ -52,8 +54,12 @@ async function showDialog() {
} }
$autoComplete.autocomplete({ $autoComplete.autocomplete({
source: await autocompleteService.getAutocompleteItems(), source: async function(request, response) {
minLength: 0, const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term));
response(result);
},
minLength: 2,
change: async () => { change: async () => {
const val = $autoComplete.val(); const val = $autoComplete.val();
const notePath = linkService.getNodePathFromLabel(val); const notePath = linkService.getNodePathFromLabel(val);
@@ -92,7 +98,16 @@ $form.submit(() => {
$dialog.dialog("close"); $dialog.dialog("close");
linkService.addLinkToEditor(linkTitle, '#' + notePath); const linkHref = '#' + notePath;
if (hasSelection()) {
const editor = noteDetailText.getEditor();
editor.execute('link', linkHref);
}
else {
linkService.addLinkToEditor(linkTitle, linkHref);
}
} }
else if (linkType === 'selected-to-current') { else if (linkType === 'selected-to-current') {
const prefix = $clonePrefix.val(); const prefix = $clonePrefix.val();
@@ -113,17 +128,21 @@ $form.submit(() => {
return false; return false;
}); });
// returns true if user selected some text, false if there's no selection
function hasSelection() {
const model = noteDetailText.getEditor().model;
const selection = model.document.selection;
return !selection.isCollapsed;
}
function linkTypeChanged() { function linkTypeChanged() {
const value = $linkTypes.filter(":checked").val(); const value = $linkTypes.filter(":checked").val();
if (value === 'html') { $linkTitleFormGroup.toggle(!hasSelection() && value === 'html');
$linkTitleFormGroup.show(); $prefixFormGroup.toggle(!hasSelection() && value !== 'html');
$prefixFormGroup.hide();
} $linkTypeDiv.toggle(!hasSelection());
else {
$linkTitleFormGroup.hide();
$prefixFormGroup.show();
}
} }
$linkTypes.change(linkTypeChanged); $linkTypes.change(linkTypeChanged);

View File

@@ -19,7 +19,7 @@ async function showDialog() {
$list.html(''); $list.html('');
for (const event of result) { for (const event of result) {
const dateTime = utils.formatDateTime(utils.parseDate(event.dateAdded)); const dateTime = utils.formatDateTime(utils.parseDate(event.dateCreated));
if (event.noteId) { if (event.noteId) {
const noteLink = await linkService.createNoteLink(event.noteId).prop('outerHTML'); const noteLink = await linkService.createNoteLink(event.noteId).prop('outerHTML');

View File

@@ -1,104 +0,0 @@
import treeCache from "./tree_cache.js";
import treeUtils from "./tree_utils.js";
import protectedSessionHolder from './protected_session_holder.js';
async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
if (!parentNoteId) {
parentNoteId = 'root';
}
const parentNote = await treeCache.getNote(parentNoteId);
const childNotes = await parentNote.getChildNotes();
if (!childNotes.length) {
return [];
}
if (!notePath) {
notePath = '';
}
if (!titlePath) {
titlePath = '';
}
const autocompleteItems = [];
for (const childNote of childNotes) {
if (childNote.hideInAutocomplete) {
continue;
}
const childNotePath = (notePath ? (notePath + '/') : '') + childNote.noteId;
const childTitlePath = (titlePath ? (titlePath + ' / ') : '') + await treeUtils.getNoteTitle(childNote.noteId, parentNoteId);
if (!childNote.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) {
autocompleteItems.push({
value: childTitlePath + ' (' + childNotePath + ')',
label: childTitlePath
});
}
const childItems = await getAutocompleteItems(childNote.noteId, childNotePath, childTitlePath);
for (const childItem of childItems) {
autocompleteItems.push(childItem);
}
}
if (parentNoteId === 'root') {
console.log(`Generated ${autocompleteItems.length} autocomplete items`);
}
return autocompleteItems;
}
// Overrides the default autocomplete filter function to search for matched on atleast 1 word in each of the input term's words
$.ui.autocomplete.filter = (array, terms) => {
if (!terms) {
return array;
}
const startDate = new Date();
const results = [];
const tokens = terms.toLowerCase().split(" ");
for (const item of array) {
const lcLabel = item.label.toLowerCase();
const found = tokens.every(token => lcLabel.indexOf(token) !== -1);
if (!found) {
continue;
}
// this is not completely correct and might cause minor problems with note with names containing this " / "
const lastSegmentIndex = lcLabel.lastIndexOf(" / ");
if (lastSegmentIndex !== -1) {
const lastSegment = lcLabel.substr(lastSegmentIndex + 3);
// at least some token needs to be in the last segment (leaf note), otherwise this
// particular note is not that interesting (query is satisfied by parent note)
const foundInLastSegment = tokens.some(token => lastSegment.indexOf(token) !== -1);
if (!foundInLastSegment) {
continue;
}
}
results.push(item);
if (results.length > 100) {
break;
}
}
console.log("Search took " + (new Date().getTime() - startDate.getTime()) + "ms");
return results;
};
export default {
getAutocompleteItems
};

View File

@@ -35,6 +35,7 @@ import libraryLoader from "./library_loader.js";
// required for CKEditor image upload plugin // required for CKEditor image upload plugin
window.glob.getCurrentNode = treeService.getCurrentNode; window.glob.getCurrentNode = treeService.getCurrentNode;
window.glob.getHeaders = server.getHeaders; window.glob.getHeaders = server.getHeaders;
window.glob.showAddLinkDialog = addLinkDialog.showDialog;
// required for ESLint plugin // required for ESLint plugin
window.glob.getCurrentNote = noteDetailService.getCurrentNote; window.glob.getCurrentNote = noteDetailService.getCurrentNote;

View File

@@ -94,25 +94,31 @@ const contextMenuOptions = {
{title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"}, {title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"},
{title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"}, {title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"},
{title: "----"}, {title: "----"},
{title: "Export branch", cmd: "exportBranch", uiIcon: " ui-icon-arrowthick-1-ne"}, {title: "Export branch", cmd: "exportBranch", uiIcon: " ui-icon-arrowthick-1-ne", children: [
{title: "Import into branch", cmd: "importBranch", uiIcon: "ui-icon-arrowthick-1-sw"}, {title: "Native&nbsp;Tar", cmd: "exportBranchToTar"},
{title: "OPML", cmd: "exportBranchToOpml"}
]},
{title: "Import into branch (tar, opml)", cmd: "importBranch", uiIcon: "ui-icon-arrowthick-1-sw"},
{title: "----"}, {title: "----"},
{title: "Collapse branch <kbd>Alt+-</kbd>", cmd: "collapseBranch", uiIcon: "ui-icon-minus"}, {title: "Collapse branch <kbd>Alt+-</kbd>", cmd: "collapseBranch", uiIcon: "ui-icon-minus"},
{title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"}, {title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"},
{title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"} {title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"}
], ],
beforeOpen: async (event, ui) => { beforeOpen: async (event, ui) => {
const node = $.ui.fancytree.getNode(ui.target); const node = $.ui.fancytree.getNode(ui.target);
const branch = await treeCache.getBranch(node.data.branchId); const branch = await treeCache.getBranch(node.data.branchId);
const note = await treeCache.getNote(node.data.noteId); const note = await treeCache.getNote(node.data.noteId);
const parentNote = await treeCache.getNote(branch.parentNoteId); const parentNote = await treeCache.getNote(branch.parentNoteId);
const isNotRoot = note.noteId !== 'root';
// Modify menu entries depending on node status // Modify menu entries depending on node status
$tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0 && (!parentNote || parentNote.type !== 'search')); $tree.contextmenu("enableEntry", "insertNoteHere", isNotRoot && parentNote.type !== 'search');
$tree.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0 && note.type !== 'search');
$tree.contextmenu("enableEntry", "insertNoteHere", !parentNote || parentNote.type !== 'search');
$tree.contextmenu("enableEntry", "insertChildNote", note.type !== 'search'); $tree.contextmenu("enableEntry", "insertChildNote", note.type !== 'search');
$tree.contextmenu("enableEntry", "delete", isNotRoot);
$tree.contextmenu("enableEntry", "copy", isNotRoot);
$tree.contextmenu("enableEntry", "cut", isNotRoot);
$tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0 && isNotRoot && parentNote.type !== 'search');
$tree.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0 && note.type !== 'search');
$tree.contextmenu("enableEntry", "importBranch", note.type !== 'search'); $tree.contextmenu("enableEntry", "importBranch", note.type !== 'search');
$tree.contextmenu("enableEntry", "exportBranch", note.type !== 'search'); $tree.contextmenu("enableEntry", "exportBranch", note.type !== 'search');
@@ -159,8 +165,11 @@ const contextMenuOptions = {
else if (ui.cmd === "delete") { else if (ui.cmd === "delete") {
treeChangesService.deleteNodes(treeService.getSelectedNodes(true)); treeChangesService.deleteNodes(treeService.getSelectedNodes(true));
} }
else if (ui.cmd === "exportBranch") { else if (ui.cmd === "exportBranchToTar") {
exportService.exportBranch(node.data.noteId); exportService.exportBranch(node.data.noteId, 'tar');
}
else if (ui.cmd === "exportBranchToOpml") {
exportService.exportBranch(node.data.noteId, 'opml');
} }
else if (ui.cmd === "importBranch") { else if (ui.cmd === "importBranch") {
exportService.importBranch(node.data.noteId); exportService.importBranch(node.data.noteId);

View File

@@ -12,6 +12,7 @@ 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 searchTreeService from "./search_tree.js";
import labelsDialog from "../dialogs/labels.js"; import labelsDialog from "../dialogs/labels.js";
import protectedSessionService from "./protected_session.js";
function registerEntrypoints() { function registerEntrypoints() {
// hot keys are active also inside inputs and content editables // hot keys are active also inside inputs and content editables
@@ -31,6 +32,9 @@ function registerEntrypoints() {
$("#recent-changes-button").click(recentChangesDialog.showDialog); $("#recent-changes-button").click(recentChangesDialog.showDialog);
$("#protected-session-on").click(protectedSessionService.enterProtectedSession);
$("#protected-session-off").click(protectedSessionService.leaveProtectedSession);
$("#recent-notes-button").click(recentNotesDialog.showDialog); $("#recent-notes-button").click(recentNotesDialog.showDialog);
utils.bindShortcut('ctrl+e', recentNotesDialog.showDialog); utils.bindShortcut('ctrl+e', recentNotesDialog.showDialog);
@@ -45,6 +49,10 @@ function registerEntrypoints() {
utils.bindShortcut('alt+o', sqlConsoleDialog.showDialog); utils.bindShortcut('alt+o', sqlConsoleDialog.showDialog);
if (utils.isElectron()) { if (utils.isElectron()) {
$("#history-navigation").show();
$("#history-back-button").click(window.history.back);
$("#history-forward-button").click(window.history.forward);
utils.bindShortcut('alt+left', window.history.back); utils.bindShortcut('alt+left', window.history.back);
utils.bindShortcut('alt+right', window.history.forward); utils.bindShortcut('alt+right', window.history.forward);
} }

View File

@@ -3,9 +3,9 @@ import protectedSessionHolder from './protected_session_holder.js';
import utils from './utils.js'; import utils from './utils.js';
import server from './server.js'; import server from './server.js';
function exportBranch(noteId) { function exportBranch(noteId, format) {
const url = utils.getHost() + "/api/notes/" + noteId + "/export?protectedSessionId=" const url = utils.getHost() + "/api/notes/" + noteId + "/export/" + format +
+ encodeURIComponent(protectedSessionHolder.getProtectedSessionId()); "?protectedSessionId=" + encodeURIComponent(protectedSessionHolder.getProtectedSessionId());
utils.download(url); utils.download(url);
} }
@@ -29,7 +29,7 @@ $("#import-upload").change(async function() {
type: 'POST', type: 'POST',
contentType: false, // NEEDED, DON'T OMIT THIS contentType: false, // NEEDED, DON'T OMIT THIS
processData: false, // NEEDED, DON'T OMIT THIS processData: false, // NEEDED, DON'T OMIT THIS
}); }).fail((xhr, status, error) => alert('Import error: ' + xhr.responseText));
await treeService.reload(); await treeService.reload();
}); });

View File

@@ -64,24 +64,27 @@ function focus() {
} }
async function executeCurrentNote() { async function executeCurrentNote() {
if (noteDetailService.getCurrentNoteType() === 'code') { // ctrl+enter is also used elsewhere so make sure we're running only when appropriate
// make sure note is saved so we load latest changes if (noteDetailService.getCurrentNoteType() !== 'code') {
await noteDetailService.saveNoteIfChanged(); return;
const currentNote = noteDetailService.getCurrentNote();
if (currentNote.mime.endsWith("env=frontend")) {
const bundle = await server.get('script/bundle/' + noteDetailService.getCurrentNoteId());
bundleService.executeBundle(bundle);
}
if (currentNote.mime.endsWith("env=backend")) {
await server.post('script/run/' + noteDetailService.getCurrentNoteId());
}
infoService.showMessage("Note executed");
} }
// make sure note is saved so we load latest changes
await noteDetailService.saveNoteIfChanged();
const currentNote = noteDetailService.getCurrentNote();
if (currentNote.mime.endsWith("env=frontend")) {
const bundle = await server.get('script/bundle/' + noteDetailService.getCurrentNoteId());
bundleService.executeBundle(bundle);
}
if (currentNote.mime.endsWith("env=backend")) {
await server.post('script/run/' + noteDetailService.getCurrentNoteId());
}
infoService.showMessage("Note executed");
} }
$(document).bind('keydown', "ctrl+return", executeCurrentNote); $(document).bind('keydown', "ctrl+return", executeCurrentNote);

View File

@@ -1,21 +1,73 @@
import bundleService from "./bundle.js"; import bundleService from "./bundle.js";
import server from "./server.js"; import server from "./server.js";
import noteDetailService from "./note_detail.js"; import noteDetailService from "./note_detail.js";
import noteDetailCodeService from "./note_detail_code.js";
const $noteDetailCode = $('#note-detail-code');
const $noteDetailRender = $('#note-detail-render'); const $noteDetailRender = $('#note-detail-render');
const $toggleEditButton = $('#toggle-edit-button');
const $renderButton = $('#render-button');
let codeEditorInitialized;
async function show() { async function show() {
codeEditorInitialized = false;
$noteDetailRender.show(); $noteDetailRender.show();
await render();
}
async function toggleEdit() {
if ($noteDetailCode.is(":visible")) {
$noteDetailCode.hide();
}
else {
if (!codeEditorInitialized) {
await noteDetailCodeService.show();
// because we can't properly scroll only the editor without scrolling the rendering
// we limit its height
$noteDetailCode.find('.CodeMirror').css('height', '300');
codeEditorInitialized = true;
}
else {
$noteDetailCode.show();
}
}
}
$toggleEditButton.click(toggleEdit);
$renderButton.click(render);
async function render() {
// ctrl+enter is also used elsewhere so make sure we're running only when appropriate
if (noteDetailService.getCurrentNoteType() !== 'render') {
return;
}
if (codeEditorInitialized) {
await noteDetailService.saveNoteIfChanged();
}
const bundle = await server.get('script/bundle/' + noteDetailService.getCurrentNoteId()); const bundle = await server.get('script/bundle/' + noteDetailService.getCurrentNoteId());
$noteDetailRender.html(bundle.html); $noteDetailRender.html(bundle.html);
// if the note is empty, it doesn't make sense to do render-only since nothing will be rendered
if (!bundle.html.trim()) {
toggleEdit();
}
await bundleService.executeBundle(bundle); await bundleService.executeBundle(bundle);
} }
$(document).bind('keydown', "ctrl+return", render);
export default { export default {
show, show,
getContent: () => null, getContent: noteDetailCodeService.getContent,
focus: () => null focus: () => null
} }

View File

@@ -4,6 +4,9 @@ import server from './server.js';
import infoService from "./info.js"; import infoService from "./info.js";
const $executeScriptButton = $("#execute-script-button"); const $executeScriptButton = $("#execute-script-button");
const $toggleEditButton = $('#toggle-edit-button');
const $renderButton = $('#render-button');
const noteTypeModel = new NoteTypeModel(); const noteTypeModel = new NoteTypeModel();
function NoteTypeModel() { function NoteTypeModel() {
@@ -107,7 +110,7 @@ function NoteTypeModel() {
this.selectRender = function() { this.selectRender = function() {
self.type('render'); self.type('render');
self.mime(''); self.mime('text/html');
save(); save();
}; };
@@ -128,6 +131,9 @@ function NoteTypeModel() {
this.updateExecuteScriptButtonVisibility = function() { this.updateExecuteScriptButtonVisibility = function() {
$executeScriptButton.toggle(self.mime().startsWith('application/javascript')); $executeScriptButton.toggle(self.mime().startsWith('application/javascript'));
$toggleEditButton.toggle(self.type() === 'render');
$renderButton.toggle(self.type() === 'render');
} }
} }

View File

@@ -11,9 +11,23 @@ const $password = $("#protected-session-password");
const $noteDetailWrapper = $("#note-detail-wrapper"); const $noteDetailWrapper = $("#note-detail-wrapper");
const $protectButton = $("#protect-button"); const $protectButton = $("#protect-button");
const $unprotectButton = $("#unprotect-button"); const $unprotectButton = $("#unprotect-button");
const $protectedSessionOnButton = $("#protected-session-on");
const $protectedSessionOffButton = $("#protected-session-off");
let protectedSessionDeferred = null; let protectedSessionDeferred = null;
async function enterProtectedSession() {
if (!protectedSessionHolder.isProtectedSessionAvailable()) {
await ensureProtectedSession(true, true);
}
}
async function leaveProtectedSession() {
if (protectedSessionHolder.isProtectedSessionAvailable()) {
utils.reloadApp();
}
}
function ensureProtectedSession(requireProtectedSession, modal) { function ensureProtectedSession(requireProtectedSession, modal) {
const dfd = $.Deferred(); const dfd = $.Deferred();
@@ -46,7 +60,7 @@ async function setupProtectedSession() {
const password = $password.val(); const password = $password.val();
$password.val(""); $password.val("");
const response = await enterProtectedSession(password); const response = await enterProtectedSessionOnServer(password);
if (!response.success) { if (!response.success) {
infoService.showError("Wrong password."); infoService.showError("Wrong password.");
@@ -67,6 +81,9 @@ async function setupProtectedSession() {
protectedSessionDeferred.resolve(); protectedSessionDeferred.resolve();
$protectedSessionOnButton.addClass('active');
$protectedSessionOffButton.removeClass('active');
protectedSessionDeferred = null; protectedSessionDeferred = null;
} }
} }
@@ -81,7 +98,7 @@ function ensureDialogIsClosed() {
$password.val(''); $password.val('');
} }
async function enterProtectedSession(password) { async function enterProtectedSessionOnServer(password) {
return await server.post('login/protected', { return await server.post('login/protected', {
password: password password: password
}); });
@@ -138,5 +155,7 @@ export default {
protectNoteAndSendToServer, protectNoteAndSendToServer,
unprotectNoteAndSendToServer, unprotectNoteAndSendToServer,
protectBranch, protectBranch,
ensureDialogIsClosed ensureDialogIsClosed,
enterProtectedSession,
leaveProtectedSession
}; };

View File

@@ -17,11 +17,11 @@ import Branch from '../entities/branch.js';
import NoteShort from '../entities/note_short.js'; import NoteShort from '../entities/note_short.js';
const $tree = $("#tree"); const $tree = $("#tree");
const $parentList = $("#parent-list");
const $parentListList = $("#parent-list-inner");
const $createTopLevelNoteButton = $("#create-top-level-note-button"); const $createTopLevelNoteButton = $("#create-top-level-note-button");
const $collapseTreeButton = $("#collapse-tree-button"); const $collapseTreeButton = $("#collapse-tree-button");
const $scrollToCurrentNoteButton = $("#scroll-to-current-note-button"); const $scrollToCurrentNoteButton = $("#scroll-to-current-note-button");
const $notePathList = $("#note-path-list");
const $notePathCount = $("#note-path-count");
let startNotePath = null; let startNotePath = null;
@@ -81,11 +81,15 @@ async function expandToNote(notePath, expandOpts) {
const noteId = treeUtils.getNoteIdFromNotePath(notePath); const noteId = treeUtils.getNoteIdFromNotePath(notePath);
let parentNoteId = 'root'; let parentNoteId = 'none';
for (const childNoteId of runPath) { for (const childNoteId of runPath) {
const node = getNodesByNoteId(childNoteId).find(node => node.data.parentNoteId === parentNoteId); const node = getNodesByNoteId(childNoteId).find(node => node.data.parentNoteId === parentNoteId);
if (!node) {
console.log(`Can't find node for noteId=${childNoteId} with parentNoteId=${parentNoteId}`);
}
if (childNoteId === noteId) { if (childNoteId === noteId) {
return node; return node;
} }
@@ -115,7 +119,10 @@ async function getRunPath(notePath) {
utils.assertArguments(notePath); utils.assertArguments(notePath);
const path = notePath.split("/").reverse(); const path = notePath.split("/").reverse();
path.push('root');
if (!path.includes("root")) {
path.push('root');
}
const effectivePath = []; const effectivePath = [];
let childNoteId = null; let childNoteId = null;
@@ -151,6 +158,8 @@ async function getRunPath(notePath) {
for (const noteId of pathToRoot) { for (const noteId of pathToRoot) {
effectivePath.push(noteId); effectivePath.push(noteId);
} }
effectivePath.push('root');
} }
break; break;
@@ -162,7 +171,7 @@ async function getRunPath(notePath) {
} }
} }
if (parentNoteId === 'root') { if (parentNoteId === 'none') {
break; break;
} }
else { else {
@@ -180,16 +189,13 @@ async function showParentList(noteId, node) {
const note = await treeCache.getNote(noteId); const note = await treeCache.getNote(noteId);
const parents = await note.getParentNotes(); const parents = await note.getParentNotes();
if (!parents.length) { $notePathCount.html(parents.length + " path" + (parents.length > 0 ? "s" : ""));
infoService.throwError("Can't find parents for noteId=" + noteId);
}
if (parents.length <= 1) { if (parents.length <= 1) {
$parentList.hide();
} }
else { else {
$parentList.show(); //$notePathList.show();
$parentListList.empty(); $notePathList.empty();
for (const parentNote of parents) { for (const parentNote of parents) {
const parentNotePath = await getSomeNotePath(parentNote); const parentNotePath = await getSomeNotePath(parentNote);
@@ -197,16 +203,13 @@ async function showParentList(noteId, node) {
const notePath = parentNotePath ? (parentNotePath + '/' + noteId) : noteId; const notePath = parentNotePath ? (parentNotePath + '/' + noteId) : noteId;
const title = await treeUtils.getNotePathTitle(notePath); const title = await treeUtils.getNotePathTitle(notePath);
let item; const item = $("<li/>").append(await linkService.createNoteLink(notePath, title));
if (node.getParent().data.noteId === parentNote.noteId) { if (node.getParent().data.noteId === parentNote.noteId) {
item = $("<span/>").attr("title", "Current note").append(title); item.addClass("current");
}
else {
item = await linkService.createNoteLink(notePath, title);
} }
$parentListList.append($("<li/>").append(item)); $notePathList.append(item);
} }
} }
} }
@@ -294,6 +297,7 @@ function initFancyTree(tree) {
extensions: ["hotkeys", "filter", "dnd", "clones"], extensions: ["hotkeys", "filter", "dnd", "clones"],
source: tree, source: tree,
scrollParent: $tree, scrollParent: $tree,
minExpandLevel: 2, // root can't be collapsed
click: (event, data) => { click: (event, data) => {
const targetType = data.targetType; const targetType = data.targetType;
const node = data.node; const node = data.node;

View File

@@ -10,7 +10,7 @@ async function prepareTree(noteRows, branchRows, relations) {
treeCache.load(noteRows, branchRows, relations); treeCache.load(noteRows, branchRows, relations);
return await prepareRealBranch(await treeCache.getNote('root')); return [ await prepareNode(await treeCache.getBranch('root')) ];
} }
async function prepareBranch(note) { async function prepareBranch(note) {
@@ -22,6 +22,35 @@ async function prepareBranch(note) {
} }
} }
async function prepareNode(branch) {
const note = await branch.getNote();
const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title;
const node = {
noteId: note.noteId,
parentNoteId: branch.parentNoteId,
branchId: branch.branchId,
isProtected: note.isProtected,
title: utils.escapeHtml(title),
extraClasses: await getExtraClasses(note),
refKey: note.noteId,
expanded: note.type !== 'search' && branch.isExpanded
};
if (note.hasChildren() || note.type === 'search') {
node.folder = true;
if (node.expanded && note.type !== 'search') {
node.children = await prepareRealBranch(note);
}
else {
node.lazy = true;
}
}
return node;
}
async function prepareRealBranch(parentNote) { async function prepareRealBranch(parentNote) {
utils.assertArguments(parentNote); utils.assertArguments(parentNote);
@@ -35,30 +64,7 @@ async function prepareRealBranch(parentNote) {
const noteList = []; const noteList = [];
for (const branch of childBranches) { for (const branch of childBranches) {
const note = await branch.getNote(); const node = await prepareNode(branch);
const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title;
const node = {
noteId: note.noteId,
parentNoteId: branch.parentNoteId,
branchId: branch.branchId,
isProtected: note.isProtected,
title: utils.escapeHtml(title),
extraClasses: await getExtraClasses(note),
refKey: note.noteId,
expanded: note.type !== 'search' && branch.isExpanded
};
if (note.hasChildren() || note.type === 'search') {
node.folder = true;
if (node.expanded && note.type !== 'search') {
node.children = await prepareRealBranch(note);
}
else {
node.lazy = true;
}
}
noteList.push(node); noteList.push(node);
} }
@@ -90,6 +96,10 @@ async function getExtraClasses(note) {
const extraClasses = []; const extraClasses = [];
if (note.noteId === 'root') {
extraClasses.push("tree-root");
}
if (note.isProtected) { if (note.isProtected) {
extraClasses.push("protected"); extraClasses.push("protected");
} }

View File

@@ -58,6 +58,10 @@ class TreeCache {
/** @return NoteShort */ /** @return NoteShort */
async getNote(noteId) { async getNote(noteId) {
if (noteId === 'none') {
return null;
}
return (await this.getNotes([noteId]))[0]; return (await this.getNotes([noteId]))[0];
} }
@@ -68,6 +72,10 @@ class TreeCache {
} }
addBranchRelationship(branchId, childNoteId, parentNoteId) { addBranchRelationship(branchId, childNoteId, parentNoteId) {
if (parentNoteId === 'none') { // applies only to root element
return;
}
this.childParentToBranch[childNoteId + '-' + parentNoteId] = branchId; this.childParentToBranch[childNoteId + '-' + parentNoteId] = branchId;
this.parents[childNoteId] = this.parents[childNoteId] || []; this.parents[childNoteId] = this.parents[childNoteId] || [];

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -105,6 +105,15 @@ span.fancytree-node.fancytree-active-clone:not(.fancytree-active) .fancytree-tit
font-weight: bold; font-weight: bold;
} }
span.fancytree-node.tree-root > span.fancytree-icon {
background: url("../images/icons/tree-root.png") 0 0;
}
/* first nesting level has lower left padding to avoid extra left padding. Other levels are not affected */
.ui-fancytree > li > ul {
padding-left: 5px;
}
/* By default not focused active tree item is not easily visible, this makes it more visible */ /* By default not focused active tree item is not easily visible, this makes it more visible */
span.fancytree-active:not(.fancytree-focused) .fancytree-title { span.fancytree-active:not(.fancytree-focused) .fancytree-title {
background-color: #ddd !important; background-color: #ddd !important;
@@ -166,20 +175,6 @@ div.ui-tooltip {
margin-top: 10px; margin-top: 10px;
} }
#parent-list {
display: none;
margin-left: 20px;
border-top: 2px solid #eee;
padding-top: 10px;
grid-area: parent-list;
max-height: 300px;
overflow: auto;
}
#parent-list ul {
padding-left: 20px;
}
/* /*
* .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.
@@ -240,7 +235,7 @@ div.ui-tooltip {
filter: opacity(7%); filter: opacity(7%);
} }
.dropdown-menu li:not(.divider) { #note-type .dropdown-menu li:not(.divider) {
padding: 5px; padding: 5px;
width: 200px; width: 200px;
} }
@@ -268,10 +263,20 @@ div.ui-tooltip {
#note-detail-code { #note-detail-code {
min-height: 200px; min-height: 200px;
overflow: auto;
}
#note-detail-render {
min-height: 200px;
} }
.CodeMirror { .CodeMirror {
font-family: "Liberation Mono", "Lucida Console", monospace; font-family: "Liberation Mono", "Lucida Console", monospace;
height: auto;
}
.CodeMirror-scroll {
min-height: 200px;
} }
#note-id-display { #note-id-display {
@@ -347,4 +352,21 @@ div.ui-tooltip {
#sql-console-query .CodeMirror { #sql-console-query .CodeMirror {
height: 150px; height: 150px;
}
#history-navigation {
margin: 0 20px 0 5px;
display: flex;
}
.btn {
border-color: #ddd;
}
.btn.active {
background-color: #ddd;
}
#note-path-list .current a {
font-weight: bold;
} }

View File

@@ -3,17 +3,7 @@
const sql = require('../../services/sql'); const sql = require('../../services/sql');
async function getEventLog() { async function getEventLog() {
await deleteOld(); return await sql.getRows("SELECT * FROM event_log ORDER BY dateCreated DESC");
return await sql.getRows("SELECT * FROM event_log ORDER BY dateAdded DESC");
}
async function deleteOld() {
const cutoffId = await sql.getValue("SELECT id FROM event_log ORDER BY id DESC LIMIT 1000, 1");
if (cutoffId) {
await sql.execute("DELETE FROM event_log WHERE id < ?", [cutoffId]);
}
} }
module.exports = { module.exports = {

View File

@@ -5,12 +5,85 @@ const html = require('html');
const tar = require('tar-stream'); const tar = require('tar-stream');
const sanitize = require("sanitize-filename"); const sanitize = require("sanitize-filename");
const repository = require("../../services/repository"); const repository = require("../../services/repository");
const utils = require('../../services/utils');
async function exportNote(req, res) { async function exportNote(req, res) {
const noteId = req.params.noteId; const noteId = req.params.noteId;
const format = req.params.format;
const branchId = await sql.getValue('SELECT branchId FROM branches WHERE noteId = ?', [noteId]); const branchId = await sql.getValue('SELECT branchId FROM branches WHERE noteId = ?', [noteId]);
if (format === 'tar') {
await exportToTar(branchId, res);
}
else if (format === 'opml') {
await exportToOpml(branchId, res);
}
else {
return [404, "Unrecognized export format " + format];
}
}
function escapeXmlAttribute(text) {
return text.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
}
function prepareText(text) {
const newLines = text.replace(/(<p[^>]*>|<br\s*\/?>)/g, '\n')
.replace(/&nbsp;/g, ' '); // nbsp isn't in XML standard (only HTML)
const stripped = utils.stripTags(newLines);
const escaped = escapeXmlAttribute(stripped);
return escaped.replace(/\n/g, '&#10;');
}
async function exportToOpml(branchId, res) {
const branch = await repository.getBranch(branchId);
const note = await branch.getNote();
const title = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title;
const sanitizedTitle = sanitize(title);
async function exportNoteInner(branchId) {
const branch = await repository.getBranch(branchId);
const note = await branch.getNote();
const title = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title;
const preparedTitle = prepareText(title);
const preparedContent = prepareText(note.content);
res.write(`<outline title="${preparedTitle}" text="${preparedContent}">\n`);
for (const child of await note.getChildBranches()) {
await exportNoteInner(child.branchId);
}
res.write('</outline>');
}
res.setHeader('Content-Disposition', 'file; filename="' + sanitizedTitle + '.opml"');
res.setHeader('Content-Type', 'text/x-opml');
res.write(`<?xml version="1.0" encoding="UTF-8"?>
<opml version="1.0">
<head>
<title>Trilium export</title>
</head>
<body>`);
await exportNoteInner(branchId);
res.write(`</body>
</opml>`);
res.end();
}
async function exportToTar(branchId, res) {
const pack = tar.pack(); const pack = tar.pack();
const exportedNoteIds = []; const exportedNoteIds = [];

View File

@@ -7,6 +7,79 @@ const Branch = require('../../entities/branch');
const tar = require('tar-stream'); const tar = require('tar-stream');
const stream = require('stream'); const stream = require('stream');
const path = require('path'); const path = require('path');
const parseString = require('xml2js').parseString;
async function importToBranch(req) {
const parentNoteId = req.params.parentNoteId;
const file = req.file;
const parentNote = await repository.getNote(parentNoteId);
if (!parentNote) {
return [404, `Note ${parentNoteId} doesn't exist.`];
}
const extension = path.extname(file.originalname).toLowerCase();
if (extension === '.tar') {
await importTar(file, parentNoteId);
}
else if (extension === '.opml') {
return await importOpml(file, parentNoteId);
}
else {
return [400, `Unrecognized extension ${extension}, must be .tar or .opml`];
}
}
function toHtml(text) {
if (!text) {
return '';
}
return '<p>' + text.replace(/(?:\r\n|\r|\n)/g, '</p><p>') + '</p>';
}
async function importOutline(outline, parentNoteId) {
const {note} = await noteService.createNote(parentNoteId, outline.$.title, toHtml(outline.$.text));
for (const childOutline of (outline.outline || [])) {
await importOutline(childOutline, note.noteId);
}
}
async function importOpml(file, parentNoteId) {
const xml = await new Promise(function(resolve, reject)
{
parseString(file.buffer, function (err, result) {
if (err) {
reject(err);
}
else {
resolve(result);
}
});
});
if (xml.opml.$.version !== '1.0' && xml.opml.$.version !== '1.1') {
return [400, 'Unsupported OPML version ' + xml.opml.$.version + ', 1.0 or 1.1 expected instead.'];
}
const outlines = xml.opml.body[0].outline || [];
for (const outline of outlines) {
await importOutline(outline, parentNoteId);
}
}
async function importTar(file, parentNoteId) {
const files = await parseImportFile(file);
// maps from original noteId (in tar file) to newly generated noteId
const noteIdMap = {};
await importNotes(files, parentNoteId, noteIdMap);
}
function getFileName(name) { function getFileName(name) {
let key; let key;
@@ -86,24 +159,6 @@ async function parseImportFile(file) {
}); });
} }
async function importTar(req) {
const parentNoteId = req.params.parentNoteId;
const file = req.file;
const parentNote = await repository.getNote(parentNoteId);
if (!parentNote) {
return [404, `Note ${parentNoteId} doesn't exist.`];
}
const files = await parseImportFile(file);
// maps from original noteId (in tar file) to newly generated noteId
const noteIdMap = {};
await importNotes(files, parentNoteId, noteIdMap);
}
async function importNotes(files, parentNoteId, noteIdMap) { async function importNotes(files, parentNoteId, noteIdMap) {
for (const file of files) { for (const file of files) {
if (file.meta.version !== 1) { if (file.meta.version !== 1) {
@@ -143,5 +198,5 @@ async function importNotes(files, parentNoteId, noteIdMap) {
} }
module.exports = { module.exports = {
importTar importToBranch
}; };

View File

@@ -16,7 +16,7 @@ async function getRecentNotes() {
recent_notes.isDeleted = 0 recent_notes.isDeleted = 0
AND branches.isDeleted = 0 AND branches.isDeleted = 0
ORDER BY ORDER BY
dateAccessed DESC dateCreated DESC
LIMIT 200`); LIMIT 200`);
} }
@@ -26,9 +26,7 @@ async function addRecentNote(req) {
await new RecentNote({ await new RecentNote({
branchId: branchId, branchId: branchId,
notePath: notePath, notePath: notePath
dateAccessed: dateUtils.nowDate(),
isDeleted: 0
}).save(); }).save();
await optionService.setOption('startNotePath', notePath); await optionService.setOption('startNotePath', notePath);

View File

@@ -5,11 +5,9 @@ const optionService = require('../../services/options');
const protectedSessionService = require('../../services/protected_session'); const protectedSessionService = require('../../services/protected_session');
async function getNotes(noteIds) { async function getNotes(noteIds) {
const questionMarks = noteIds.map(() => "?").join(","); const notes = await sql.getManyRows(`
const notes = await sql.getRows(`
SELECT noteId, title, isProtected, type, mime SELECT noteId, title, isProtected, type, mime
FROM notes WHERE isDeleted = 0 AND noteId IN (${questionMarks})`, noteIds); FROM notes WHERE isDeleted = 0 AND noteId IN (???)`, noteIds);
protectedSessionService.decryptNotes(notes); protectedSessionService.decryptNotes(notes);
@@ -18,11 +16,11 @@ async function getNotes(noteIds) {
} }
async function getRelations(noteIds) { async function getRelations(noteIds) {
const questionMarks = noteIds.map(() => "?").join(","); // we need to fetch both parentNoteId and noteId matches because we can have loaded child
const doubledNoteIds = noteIds.concat(noteIds); // of which only some of the parents has been loaded.
return await sql.getRows(`SELECT branchId, noteId AS 'childNoteId', parentNoteId FROM branches WHERE isDeleted = 0 return await sql.getManyRows(`SELECT branchId, noteId AS 'childNoteId', parentNoteId FROM branches WHERE isDeleted = 0
AND (parentNoteId IN (${questionMarks}) OR noteId IN (${questionMarks}))`, doubledNoteIds); AND (parentNoteId IN (???) OR noteId IN (???))`, noteIds);
} }
async function getTree() { async function getTree() {
@@ -58,12 +56,11 @@ async function load(req) {
const branchIds = req.body.branchIds; const branchIds = req.body.branchIds;
if (branchIds && branchIds.length > 0) { if (branchIds && branchIds.length > 0) {
noteIds = await sql.getColumn(`SELECT noteId FROM branches WHERE isDeleted = 0 AND branchId IN(${branchIds.map(() => "?").join(",")})`, branchIds); noteIds = (await sql.getManyRows(`SELECT noteId FROM branches WHERE isDeleted = 0 AND branchId IN(???)`, branchIds))
.map(note => note.noteId);
} }
const questionMarks = noteIds.map(() => "?").join(","); const branches = await sql.getManyRows(`SELECT * FROM branches WHERE isDeleted = 0 AND noteId IN (???)`, noteIds);
const branches = await sql.getRows(`SELECT * FROM branches WHERE isDeleted = 0 AND noteId IN (${questionMarks})`, noteIds);
const notes = await getNotes(noteIds); const notes = await getNotes(noteIds);

View File

@@ -122,8 +122,8 @@ function register(app) {
apiRoute(PUT, '/api/notes/:noteId/clone-to/:parentNoteId', cloningApiRoute.cloneNoteToParent); apiRoute(PUT, '/api/notes/:noteId/clone-to/:parentNoteId', cloningApiRoute.cloneNoteToParent);
apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
route(GET, '/api/notes/:noteId/export', [auth.checkApiAuthOrElectron], exportRoute.exportNote); route(GET, '/api/notes/:noteId/export/:format', [auth.checkApiAuthOrElectron], exportRoute.exportNote);
route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importTar, apiResultHandler); route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importToBranch, apiResultHandler);
route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware], route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware],
filesRoute.uploadFile, apiResultHandler); filesRoute.uploadFile, apiResultHandler);

View File

@@ -3,7 +3,7 @@
const build = require('./build'); const build = require('./build');
const packageJson = require('../../package'); const packageJson = require('../../package');
const APP_DB_VERSION = 93; const APP_DB_VERSION = 96;
module.exports = { module.exports = {
appVersion: packageJson.version, appVersion: packageJson.version,

View File

@@ -3,6 +3,7 @@ const sqlInit = require('./sql_init');
const eventService = require('./events'); const eventService = require('./events');
const repository = require('./repository'); const repository = require('./repository');
const protectedSessionService = require('./protected_session'); const protectedSessionService = require('./protected_session');
const utils = require('./utils');
let noteTitles; let noteTitles;
let protectedNoteTitles; let protectedNoteTitles;
@@ -14,10 +15,10 @@ const hideInAutocomplete = {};
let prefixes = {}; let prefixes = {};
async function load() { async function load() {
noteTitles = await sql.getMap(`SELECT noteId, LOWER(title) FROM notes WHERE isDeleted = 0 AND isProtected = 0`); noteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 0`);
noteIds = Object.keys(noteTitles); noteIds = Object.keys(noteTitles);
prefixes = await sql.getMap(`SELECT noteId || '-' || parentNoteId, LOWER(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 noteId, parentNoteId FROM branches WHERE isDeleted = 0`);
@@ -58,7 +59,11 @@ function getResults(query) {
} }
for (const parentNoteId of parents) { for (const parentNoteId of parents) {
const title = getNoteTitle(noteId, parentNoteId); if (hideInAutocomplete[parentNoteId]) {
continue;
}
const title = getNoteTitle(noteId, parentNoteId).toLowerCase();
const foundTokens = []; const foundTokens = [];
for (const token of tokens) { for (const token of tokens) {
@@ -109,6 +114,7 @@ function search(noteId, tokens, path, results) {
if (parentNoteId === 'root' || hideInAutocomplete[parentNoteId]) { if (parentNoteId === 'root' || hideInAutocomplete[parentNoteId]) {
continue; continue;
} }
const title = getNoteTitle(noteId, parentNoteId); const title = getNoteTitle(noteId, parentNoteId);
const foundTokens = []; const foundTokens = [];
@@ -241,7 +247,7 @@ eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, async () => {
} }
}); });
sqlInit.dbReady.then(load); sqlInit.dbReady.then(() => utils.stopWatch("Autocomplete load", load));
module.exports = { module.exports = {
getResults getResults

View File

@@ -1 +1 @@
module.exports = { buildDate:"2018-05-22T23:51:43-04:00", buildRevision: "a372cbb2dfa918084e2d447a01fca6f076ddf486" }; module.exports = { buildDate:"2018-06-02T09:39:37-04:00", buildRevision: "af529f82e5080f01b26ac7db104a8041f137dc48" };

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 = "";
@@ -56,7 +59,7 @@ async function checkContentHashes(otherHashes) {
if (hashes[key] !== otherHashes[key]) { if (hashes[key] !== otherHashes[key]) {
allChecksPassed = false; allChecksPassed = false;
await eventLogService.addEvent(`Content hash check for ${key} FAILED. Local is ${hashes[key]}, remote is ${resp.hashes[key]}`); await eventLogService.addEvent(`Content hash check for ${key} FAILED. Local is ${hashes[key]}, remote is ${otherHashes[key]}`);
if (key !== 'recent_notes') { if (key !== 'recent_notes') {
// let's not get alarmed about recent notes which get updated often and can cause failures in race conditions // let's not get alarmed about recent notes which get updated often and can cause failures in race conditions

View File

@@ -1,5 +1,6 @@
const sql = require('./sql'); const sql = require('./sql');
const dateUtils = require('./date_utils'); const dateUtils = require('./date_utils');
const utils = require('./utils');
const log = require('./log'); const log = require('./log');
async function addEvent(comment) { async function addEvent(comment) {
@@ -8,9 +9,10 @@ async function addEvent(comment) {
async function addNoteEvent(noteId, comment) { async function addNoteEvent(noteId, comment) {
await sql.insert('event_log', { await sql.insert('event_log', {
noteId : noteId, eventId: utils.newEntityId(),
comment: comment, noteId : noteId,
dateAdded: dateUtils.nowDate() comment: comment,
dateCreated: dateUtils.nowDate()
}); });
log.info("Event log for " + noteId + ": " + comment); log.info("Event log for " + noteId + ": " + comment);

View File

@@ -73,7 +73,7 @@ function getParams(params) {
} }
async function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = []) { async function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = []) {
if (!note.isJavaScript() && !note.isHtml() && note.type !== 'render') { if (!note.isJavaScript() && !note.isHtml()) {
return; return;
} }

View File

@@ -62,6 +62,26 @@ async function getValue(query, params = []) {
return row[Object.keys(row)[0]]; return row[Object.keys(row)[0]];
} }
const PARAM_LIMIT = 900; // actual limit is 999
// this is to overcome 999 limit of number of query parameters
async function getManyRows(query, params) {
let results = [];
while (params.length > 0) {
const curParams = params.slice(0, Math.max(params.length, PARAM_LIMIT));
params = params.slice(curParams.length);
let i = 1;
const questionMarks = curParams.map(() => "?" + i++).join(",");
const curQuery = query.replace(/\?\?\?/g, questionMarks);
results = results.concat(await getRows(curQuery, curParams));
}
return results;
}
async function getRows(query, params = []) { async function getRows(query, params = []) {
return await wrap(async db => db.all(query, ...params)); return await wrap(async db => db.all(query, ...params));
} }
@@ -179,6 +199,7 @@ module.exports = {
getRow, getRow,
getRowOrNull, getRowOrNull,
getRows, getRows,
getManyRows,
getMap, getMap,
getColumn, getColumn,
execute, execute,

View File

@@ -94,6 +94,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

@@ -207,13 +207,12 @@ const primaryKeys = {
"notes": "noteId", "notes": "noteId",
"branches": "branchId", "branches": "branchId",
"note_revisions": "noteRevisionId", "note_revisions": "noteRevisionId",
"option": "name",
"recent_notes": "branchId", "recent_notes": "branchId",
"images": "imageId", "images": "imageId",
"note_images": "noteImageId", "note_images": "noteImageId",
"labels": "labelId", "labels": "labelId",
"api_tokens": "apiTokenId", "api_tokens": "apiTokenId",
"options": "name" "options": "optionId"
}; };
async function getEntityRow(entityName, entityId) { async function getEntityRow(entityName, entityId) {

View File

@@ -127,7 +127,7 @@ async function updateOptions(entity, sourceId) {
async function updateRecentNotes(entity, sourceId) { async function updateRecentNotes(entity, sourceId) {
const orig = await sql.getRowOrNull("SELECT * FROM recent_notes WHERE branchId = ?", [entity.branchId]); const orig = await sql.getRowOrNull("SELECT * FROM recent_notes WHERE branchId = ?", [entity.branchId]);
if (orig === null || orig.dateAccessed < entity.dateAccessed) { if (orig === null || orig.dateCreated < entity.dateCreated) {
await sql.transactional(async () => { await sql.transactional(async () => {
await sql.replace('recent_notes', entity); await sql.replace('recent_notes', entity);

View File

@@ -75,6 +75,10 @@ function toObject(array, fn) {
return obj; return obj;
} }
function stripTags(text) {
return text.replace(/<(?:.|\n)*?>/gm, '');
}
module.exports = { module.exports = {
randomSecureToken, randomSecureToken,
randomString, randomString,
@@ -88,5 +92,6 @@ module.exports = {
sanitizeSql, sanitizeSql,
stopWatch, stopWatch,
unescapeHtml, unescapeHtml,
toObject toObject,
stripTags
}; };

View File

@@ -13,10 +13,28 @@
Trilium Notes Trilium Notes
</div> </div>
<div style="flex-grow: 100;"> <div id="history-navigation" style="display: none;">
<a id="history-back-button" title="Go to previous note." class="icon-action"
style="background: url('/images/icons/back.png')"></a>
&nbsp; &nbsp;
<a id="history-forward-button" title="Go to next note." class="icon-action"
style="background: url('/images/icons/forward.png')"></a>
</div>
<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-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>
<span style="font-size: smaller">Protected session:</span>
<div class="btn-group btn-group-xs">
<button type="button" class="btn" id="protected-session-on">On</button>
<button type="button" class="btn active" id="protected-session-off">Off</button>
</div>
</div>
</div> </div>
<div id="plugin-buttons"> <div id="plugin-buttons">
@@ -69,16 +87,24 @@
</div> </div>
<div id="tree"></div> <div id="tree"></div>
<div id="parent-list">
<p><strong>Note locations:</strong></p>
<ul id="parent-list-inner"></ul>
</div>
</div> </div>
<div style="grid-area: title;"> <div style="grid-area: title;">
<div class="hide-toggle" style="display: flex; align-items: center;"> <div class="hide-toggle" style="display: flex; align-items: center;">
<div class="dropdown">
<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 class="caret"></span>
</button>
<ul id="note-path-list" class="dropdown-menu" aria-labelledby="note-path-list-button">
</ul>
</div>
<input autocomplete="off" value="" id="note-title" style="margin-left: 15px; font-size: x-large; border: 0; flex-grow: 100;" tabindex="1">
<span id="note-id-display" title="Note ID"></span>
<a title="Protect the note so that password will be required to view the note" <a title="Protect the note so that password will be required to view the note"
class="icon-action" class="icon-action"
id="protect-button" id="protect-button"
@@ -89,11 +115,15 @@
id="unprotect-button" id="unprotect-button"
style="display: none; background: url('images/icons/unlock.png')"></a> style="display: none; background: url('images/icons/unlock.png')"></a>
&nbsp; &nbsp; &nbsp;
<input autocomplete="off" value="" id="note-title" style="font-size: x-large; border: 0; flex-grow: 100;" tabindex="1"> <button class="btn btn-sm"
style="display: none; margin-right: 10px"
id="toggle-edit-button">Toggle edit</button>
<span id="note-id-display" title="Note ID"></span> <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" <button class="btn btn-sm"
style="display: none; margin-right: 10px" style="display: none; margin-right: 10px"
@@ -104,7 +134,7 @@
Type: <span data-bind="text: typeString()"></span> Type: <span data-bind="text: typeString()"></span>
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul id="note-type-dropdown" class="dropdown-menu dropdown-menu-right" aria-labelledby="dLabel"> <ul id="note-type-dropdown" class="dropdown-menu dropdown-menu-right">
<li data-bind="click: selectText, css: { selected: type() == 'text' }"><span class="check">&check;</span> <strong>Text</strong></li> <li data-bind="click: selectText, css: { selected: type() == 'text' }"><span class="check">&check;</span> <strong>Text</strong></li>
<li role="separator" class="divider"></li> <li role="separator" class="divider"></li>
<li data-bind="click: selectRender, css: { selected: type() == 'render' && mime() == '' }"><span class="check">&check;</span> <strong>Render HTML note</strong></li> <li data-bind="click: selectRender, css: { selected: type() == 'render' && mime() == '' }"><span class="check">&check;</span> <strong>Render HTML note</strong></li>
@@ -121,7 +151,7 @@
Note actions Note actions
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dLabel"> <ul class="dropdown-menu dropdown-menu-right">
<li><a id="show-note-revisions-button">Note revisions</a></li> <li><a id="show-note-revisions-button">Note revisions</a></li>
<li><a class="show-labels-button"><kbd>Alt+L</kbd> Labels</a></li> <li><a class="show-labels-button"><kbd>Alt+L</kbd> Labels</a></li>
<li><a id="show-source-button"><kbd>Ctrl+U</kbd> HTML source</a></li> <li><a id="show-source-button"><kbd>Ctrl+U</kbd> HTML source</a></li>
@@ -220,7 +250,7 @@
<div id="add-link-dialog" title="Add link" style="display: none;"> <div id="add-link-dialog" title="Add link" style="display: none;">
<form id="add-link-form"> <form id="add-link-form">
<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">
<input type="radio" name="add-link-type" value="html"/> <input type="radio" name="add-link-type" value="html"/>
add normal HTML link</label> add normal HTML link</label>

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