mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	Compare commits
	
		
			20 Commits
		
	
	
		
			v0.13.0-be
			...
			v0.14.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 31b76b23ce | ||
|  | af529f82e5 | ||
|  | fc6669d254 | ||
|  | c07785be67 | ||
|  | 80d2457b23 | ||
|  | 5dde2752d2 | ||
|  | 8bf4633cd0 | ||
|  | bd66b8a1c8 | ||
|  | be51e533fc | ||
|  | f47ae12019 | ||
|  | cab54a458f | ||
|  | a30734f1bc | ||
|  | 7ad9f7b129 | ||
|  | 40a32e6826 | ||
|  | ab0486aaf1 | ||
|  | 874593a167 | ||
|  | 03bf33630e | ||
|  | 933cce1b94 | ||
|  | 4a6ff573f8 | ||
|  | 1a737f7d19 | 
| @@ -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>""</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>""</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <column id="36" parent="7" name="dateCreated"> | ||||||
|  |       <Position>10</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>'1970-01-01T00:00:00.000Z'</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>""</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>''</DefaultExpression> |       <DefaultExpression>''</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>""</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>""</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>''</DefaultExpression> |       <DefaultExpression>''</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>''</DefaultExpression> |       <DefaultExpression>''</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>""</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>"unnamed"</DefaultExpression> |       <DefaultExpression>"unnamed"</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>""</DefaultExpression> |       <DefaultExpression>""</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>'text'</DefaultExpression> |       <DefaultExpression>'text'</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>'text/html'</DefaultExpression> |       <DefaultExpression>'text/html'</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>""</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>""</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <column id="116" parent="14" name="dateCreated"> | ||||||
|  |       <Position>6</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>'1970-01-01T00:00:00.000Z'</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <index id="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>""</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> | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								db/migrations/0094__unify_auditing_fields.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								db/migrations/0094__unify_auditing_fields.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | ALTER TABLE branches ADD dateCreated TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z'; | ||||||
|  |  | ||||||
|  | CREATE TABLE `event_log_mig` ( | ||||||
|  |   `id`	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||||
|  |   `noteId`	TEXT, | ||||||
|  |   `comment`	TEXT, | ||||||
|  |   `dateCreated`	TEXT NOT NULL | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | INSERT INTO event_log_mig (id, noteId, comment, dateCreated) | ||||||
|  | SELECT id, noteId, comment, dateAdded FROM event_log; | ||||||
|  |  | ||||||
|  | DROP TABLE event_log; | ||||||
|  | ALTER TABLE event_log_mig RENAME TO event_log; | ||||||
|  |  | ||||||
|  | ALTER TABLE options ADD dateCreated TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z'; | ||||||
|  |  | ||||||
|  | CREATE TABLE `recent_notes_mig` ( | ||||||
|  |   `branchId` TEXT NOT NULL PRIMARY KEY, | ||||||
|  |   `notePath` TEXT NOT NULL, | ||||||
|  |   hash TEXT DEFAULT "" NOT NULL, | ||||||
|  |   `dateCreated` TEXT NOT NULL, | ||||||
|  |   isDeleted INT | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | INSERT INTO recent_notes_mig (branchId, notePath, hash, dateCreated, isDeleted) | ||||||
|  | SELECT branchId, notePath, hash, dateAccessed, isDeleted FROM recent_notes; | ||||||
|  |  | ||||||
|  | DROP TABLE recent_notes; | ||||||
|  | ALTER TABLE recent_notes_mig RENAME TO recent_notes; | ||||||
							
								
								
									
										1
									
								
								db/migrations/0095__mime_type_for_render.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								db/migrations/0095__mime_type_for_render.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | UPDATE notes SET mime = 'text/html' WHERE type = 'render'; | ||||||
							
								
								
									
										29
									
								
								db/migrations/0096__unify_surrogate_keys.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								db/migrations/0096__unify_surrogate_keys.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | CREATE TABLE `event_log_mig` ( | ||||||
|  |   `eventId`	TEXT NOT NULL PRIMARY KEY, | ||||||
|  |   `noteId`	TEXT, | ||||||
|  |   `comment`	TEXT, | ||||||
|  |   `dateCreated`	TEXT NOT NULL | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | INSERT INTO event_log_mig (eventId, noteId, comment, dateCreated) | ||||||
|  | SELECT id, noteId, comment, dateCreated FROM event_log; | ||||||
|  |  | ||||||
|  | DROP TABLE event_log; | ||||||
|  | ALTER TABLE event_log_mig RENAME TO event_log; | ||||||
|  |  | ||||||
|  | create table options_mig | ||||||
|  | ( | ||||||
|  |   optionId TEXT NOT NULL PRIMARY KEY, | ||||||
|  |   name TEXT not null, | ||||||
|  |   value TEXT, | ||||||
|  |   dateModified INT, | ||||||
|  |   isSynced INTEGER default 0 not null, | ||||||
|  |   hash TEXT default "" not null, | ||||||
|  |   dateCreated TEXT default '1970-01-01T00:00:00.000Z' not null | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | INSERT INTO options_mig (optionId, name, value, dateModified, isSynced, hash, dateCreated) | ||||||
|  |   SELECT name || "_key", name, value, dateModified, isSynced, hash, dateCreated FROM options; | ||||||
|  |  | ||||||
|  | DROP TABLE options; | ||||||
|  | ALTER TABLE options_mig RENAME TO options; | ||||||
| @@ -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
									
									
									
								
							
							
						
						
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -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=" | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -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(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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() { | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
| @@ -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; | ||||||
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/back.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/public/images/icons/back.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 511 B | 
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/forward.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/public/images/icons/forward.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 511 B | 
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/tree-root.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/public/images/icons/tree-root.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 240 B | 
							
								
								
									
										1
									
								
								src/public/images/trilium.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/public/images/trilium.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path d="M63.966,45.043c0.008-0.009,0.021-0.021,0.027-0.029c0.938-1.156-0.823-13.453-5.063-20.125  c-1.389-2.186-2.239-3.423-3.219-4.719c-3.907-5.166-6-6.125-6-6.125S35.732,24.78,36.149,44.315  c-1.754,0.065-11.218,7.528-14.826,14.388c-1.206,2.291-1.856,3.645-2.493,5.141c-2.539,5.957-2.33,8.25-2.33,8.25  s16.271,6.79,33.014-3.294c0.007,0.021,0.013,0.046,0.02,0.063c0.537,1.389,12.08,5.979,19.976,5.621  c2.587-0.116,4.084-0.238,5.696-0.444c6.424-0.818,8.298-2.157,8.298-2.157S81.144,54.396,63.966,45.043z M50.787,65.343  c1.059-1.183,4.648-5.853,0.995-11.315c-0.253-0.377-0.496-0.236-0.496-0.236s0.063,10.822-5.162,12.359  c-5.225,1.537-13.886,4.4-20.427,0.455C25,66.186,26.924,53.606,38.544,47.229c0.546,1.599,2.836,6.854,9.292,6.409  c0.453-0.031,0.453-0.313,0.453-0.313s-9.422-5.328-8.156-10.625s3.089-14.236,9.766-17.948c0.714-0.397,10.746,7.593,10.417,20.94  c-1.606-0.319-7.377-1.004-10.226,4.864c-0.198,0.409,0.046,0.549,0.046,0.549s9.31-5.521,13.275-1.789  c3.965,3.733,10.813,9.763,10.71,17.4C74.111,67.533,62.197,72.258,50.787,65.343z M35.613,35.145c0,0-0.991,3.241-0.603,7.524  l-13.393-7.524C21.618,35.145,27.838,30.931,35.613,35.145z M21.193,36.03l13.344,7.612c-3.872,1.872-6.142,4.388-6.142,4.388  C20.78,43.531,21.193,36.03,21.193,36.03z M72.287,49.064c0,0-2.321-2.471-6.23-4.263l13.187-7.881  C79.243,36.92,79.808,44.413,72.287,49.064z M78.687,36.113l-13.237,7.794c0.3-4.291-0.754-7.511-0.754-7.511  C72.383,32.025,78.687,36.113,78.687,36.113z M42.076,73.778c0,0,3.309-0.737,6.845-3.185l0.056,15.361  C48.977,85.955,42.244,82.621,42.076,73.778z M49.956,85.888L50,70.526c3.539,2.445,6.846,3.181,6.846,3.181  C56.686,82.551,49.956,85.888,49.956,85.888z"></path></svg> | ||||||
| After Width: | Height: | Size: 1.8 KiB | 
| @@ -2,7 +2,8 @@ import cloningService from '../services/cloning.js'; | |||||||
| import linkService from '../services/link.js'; | import linkService from '../services/link.js'; | ||||||
| import noteDetailService from '../services/note_detail.js'; | import noteDetailService from '../services/note_detail.js'; | ||||||
| import treeUtils from '../services/tree_utils.js'; | import treeUtils from '../services/tree_utils.js'; | ||||||
| import autocompleteService from '../services/autocomplete.js'; | import 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); | ||||||
|   | |||||||
| @@ -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'); | ||||||
|   | |||||||
| @@ -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 |  | ||||||
| }; |  | ||||||
							
								
								
									
										1
									
								
								src/public/javascripts/services/bootstrap.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								src/public/javascripts/services/bootstrap.js
									
									
									
									
										vendored
									
									
								
							| @@ -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; | ||||||
|   | |||||||
| @@ -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 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); | ||||||
|   | |||||||
| @@ -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); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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(); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -64,7 +64,11 @@ 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 | ||||||
|  |     if (noteDetailService.getCurrentNoteType() !== 'code') { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // make sure note is saved so we load latest changes |     // make sure note is saved so we load latest changes | ||||||
|     await noteDetailService.saveNoteIfChanged(); |     await noteDetailService.saveNoteIfChanged(); | ||||||
|  |  | ||||||
| @@ -81,7 +85,6 @@ async function executeCurrentNote() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     infoService.showMessage("Note executed"); |     infoService.showMessage("Note executed"); | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| $(document).bind('keydown', "ctrl+return", executeCurrentNote); | $(document).bind('keydown', "ctrl+return", executeCurrentNote); | ||||||
|   | |||||||
| @@ -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 | ||||||
| } | } | ||||||
| @@ -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'); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
| }; | }; | ||||||
| @@ -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(); | ||||||
|  |  | ||||||
|  |     if (!path.includes("root")) { | ||||||
|         path.push('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; | ||||||
|   | |||||||
| @@ -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,19 +22,7 @@ async function prepareBranch(note) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function prepareRealBranch(parentNote) { | async function prepareNode(branch) { | ||||||
|     utils.assertArguments(parentNote); |  | ||||||
|  |  | ||||||
|     const childBranches = await parentNote.getChildBranches(); |  | ||||||
|  |  | ||||||
|     if (!childBranches) { |  | ||||||
|         messagingService.logError(`No children for ${parentNote}. This shouldn't happen.`); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const noteList = []; |  | ||||||
|  |  | ||||||
|     for (const branch of childBranches) { |  | ||||||
|     const note = await branch.getNote(); |     const note = await branch.getNote(); | ||||||
|     const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title; |     const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title; | ||||||
|  |  | ||||||
| @@ -60,6 +48,24 @@ async function prepareRealBranch(parentNote) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     return node; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function prepareRealBranch(parentNote) { | ||||||
|  |     utils.assertArguments(parentNote); | ||||||
|  |  | ||||||
|  |     const childBranches = await parentNote.getChildBranches(); | ||||||
|  |  | ||||||
|  |     if (!childBranches) { | ||||||
|  |         messagingService.logError(`No children for ${parentNote}. This shouldn't happen.`); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const noteList = []; | ||||||
|  |  | ||||||
|  |     for (const branch of childBranches) { | ||||||
|  |         const node = await prepareNode(branch); | ||||||
|  |  | ||||||
|         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"); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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] || []; | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								src/public/libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/public/libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -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 { | ||||||
| @@ -348,3 +353,20 @@ 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; | ||||||
|  | } | ||||||
| @@ -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 = { | ||||||
|   | |||||||
| @@ -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, '&') | ||||||
|  |         .replace(/</g, '<') | ||||||
|  |         .replace(/>/g, '>') | ||||||
|  |         .replace(/"/g, '"') | ||||||
|  |         .replace(/'/g, '''); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function prepareText(text) { | ||||||
|  |     const newLines = text.replace(/(<p[^>]*>|<br\s*\/?>)/g, '\n') | ||||||
|  |                          .replace(/ /g, ' '); // nbsp isn't in XML standard (only HTML) | ||||||
|  |  | ||||||
|  |     const stripped = utils.stripTags(newLines); | ||||||
|  |  | ||||||
|  |     const escaped = escapeXmlAttribute(stripped); | ||||||
|  |  | ||||||
|  |     return escaped.replace(/\n/g, '
'); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 = []; | ||||||
|   | |||||||
| @@ -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 | ||||||
| }; | }; | ||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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" }; | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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', { | ||||||
|  |         eventId: utils.newEntityId(), | ||||||
|         noteId : noteId, |         noteId : noteId, | ||||||
|         comment: comment, |         comment: comment, | ||||||
|        dateAdded: dateUtils.nowDate() |         dateCreated: dateUtils.nowDate() | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     log.info("Event log for " + noteId + ": " + comment); |     log.info("Event log for " + noteId + ": " + comment); | ||||||
|   | |||||||
| @@ -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; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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) { | ||||||
|   | |||||||
| @@ -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); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
| }; | }; | ||||||
| @@ -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> | ||||||
|  |  | ||||||
|  |               | ||||||
|  |  | ||||||
|  |           <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> | ||||||
|  |  | ||||||
|             |               | ||||||
|  |  | ||||||
|           <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">✓</span> <strong>Text</strong></li> |               <li data-bind="click: selectText, css: { selected: type() == 'text' }"><span class="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">✓</span> <strong>Render HTML note</strong></li> |               <li data-bind="click: selectRender, css: { selected: type() == 'render' && mime() == '' }"><span class="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> | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								src/www
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								src/www
									
									
									
									
									
								
							| @@ -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'); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user