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"?> | ||||
| <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"> | ||||
|       <ServerVersion>3.16.1</ServerVersion> | ||||
|     </root> | ||||
| @@ -50,546 +50,616 @@ | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </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> | ||||
|       <ColNames>apiTokenId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="25" parent="6"> | ||||
|     <key id="26" parent="6"> | ||||
|       <ColNames>apiTokenId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_api_tokens_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="26" parent="7" name="branchId"> | ||||
|     <column id="27" parent="7" name="branchId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="27" parent="7" name="noteId"> | ||||
|     <column id="28" parent="7" name="noteId"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="28" parent="7" name="parentNoteId"> | ||||
|     <column id="29" parent="7" name="parentNoteId"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="29" parent="7" name="notePosition"> | ||||
|     <column id="30" parent="7" name="notePosition"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>INTEGER|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="30" parent="7" name="prefix"> | ||||
|     <column id="31" parent="7" name="prefix"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|     </column> | ||||
|     <column id="31" parent="7" name="isExpanded"> | ||||
|     <column id="32" parent="7" name="isExpanded"> | ||||
|       <Position>6</Position> | ||||
|       <DataType>BOOLEAN|0s</DataType> | ||||
|     </column> | ||||
|     <column id="32" parent="7" name="isDeleted"> | ||||
|     <column id="33" parent="7" name="isDeleted"> | ||||
|       <Position>7</Position> | ||||
|       <DataType>INTEGER|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="33" parent="7" name="dateModified"> | ||||
|     <column id="34" parent="7" name="dateModified"> | ||||
|       <Position>8</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <index id="34" parent="7" name="sqlite_autoindex_branches_1"> | ||||
|     <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> | ||||
|       <ColNames>branchId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="35" parent="7" name="IDX_branches_noteId_parentNoteId"> | ||||
|     <index id="38" parent="7" name="IDX_branches_noteId_parentNoteId"> | ||||
|       <ColNames>noteId | ||||
| parentNoteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="36" parent="7" name="IDX_branches_noteId"> | ||||
|     <index id="39" parent="7" name="IDX_branches_noteId"> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </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> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="38" parent="8" name="id"> | ||||
|     <column id="42" parent="8" name="id"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>INTEGER|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <SequenceIdentity>1</SequenceIdentity> | ||||
|     </column> | ||||
|     <column id="39" parent="8" name="noteId"> | ||||
|     <column id="43" parent="8" name="noteId"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|     </column> | ||||
|     <column id="40" parent="8" name="comment"> | ||||
|     <column id="44" parent="8" name="comment"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|     </column> | ||||
|     <column id="41" parent="8" name="dateAdded"> | ||||
|     <column id="45" parent="8" name="dateCreated"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <key id="42" parent="8"> | ||||
|     <key id="46" parent="8"> | ||||
|       <ColNames>id</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|     </key> | ||||
|     <column id="43" parent="9" name="imageId"> | ||||
|     <column id="47" parent="9" name="imageId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="44" parent="9" name="format"> | ||||
|     <column id="48" parent="9" name="format"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="45" parent="9" name="checksum"> | ||||
|     <column id="49" parent="9" name="checksum"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="46" parent="9" name="name"> | ||||
|     <column id="50" parent="9" name="name"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="47" parent="9" name="data"> | ||||
|     <column id="51" parent="9" name="data"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>BLOB|0s</DataType> | ||||
|     </column> | ||||
|     <column id="48" parent="9" name="isDeleted"> | ||||
|     <column id="52" parent="9" name="isDeleted"> | ||||
|       <Position>6</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="49" parent="9" name="dateModified"> | ||||
|     <column id="53" parent="9" name="dateModified"> | ||||
|       <Position>7</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="50" parent="9" name="dateCreated"> | ||||
|     <column id="54" parent="9" name="dateCreated"> | ||||
|       <Position>8</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </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> | ||||
|       <ColNames>imageId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="52" parent="9"> | ||||
|     <key id="57" parent="9"> | ||||
|       <ColNames>imageId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="53" parent="10" name="labelId"> | ||||
|     <column id="58" parent="10" name="labelId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="54" parent="10" name="noteId"> | ||||
|     <column id="59" parent="10" name="noteId"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="55" parent="10" name="name"> | ||||
|     <column id="60" parent="10" name="name"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="56" parent="10" name="value"> | ||||
|     <column id="61" parent="10" name="value"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>''</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="57" parent="10" name="position"> | ||||
|     <column id="62" parent="10" name="position"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="58" parent="10" name="dateCreated"> | ||||
|     <column id="63" parent="10" name="dateCreated"> | ||||
|       <Position>6</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="59" parent="10" name="dateModified"> | ||||
|     <column id="64" parent="10" name="dateModified"> | ||||
|       <Position>7</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="60" parent="10" name="isDeleted"> | ||||
|     <column id="65" parent="10" name="isDeleted"> | ||||
|       <Position>8</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </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> | ||||
|       <ColNames>labelId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="62" parent="10" name="IDX_labels_noteId"> | ||||
|     <index id="68" parent="10" name="IDX_labels_noteId"> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="63" parent="10" name="IDX_labels_name_value"> | ||||
|     <index id="69" parent="10" name="IDX_labels_name_value"> | ||||
|       <ColNames>name | ||||
| value</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="64" parent="10"> | ||||
|     <key id="70" parent="10"> | ||||
|       <ColNames>labelId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="65" parent="11" name="noteImageId"> | ||||
|     <column id="71" parent="11" name="noteImageId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="66" parent="11" name="noteId"> | ||||
|     <column id="72" parent="11" name="noteId"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="67" parent="11" name="imageId"> | ||||
|     <column id="73" parent="11" name="imageId"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="68" parent="11" name="isDeleted"> | ||||
|     <column id="74" parent="11" name="isDeleted"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="69" parent="11" name="dateModified"> | ||||
|     <column id="75" parent="11" name="dateModified"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="70" parent="11" name="dateCreated"> | ||||
|     <column id="76" parent="11" name="dateCreated"> | ||||
|       <Position>6</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </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> | ||||
|       <ColNames>noteImageId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </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 | ||||
| imageId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="73" parent="11" name="IDX_note_images_noteId"> | ||||
|     <index id="80" parent="11" name="IDX_note_images_noteId"> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="74" parent="11" name="IDX_note_images_imageId"> | ||||
|     <index id="81" parent="11" name="IDX_note_images_imageId"> | ||||
|       <ColNames>imageId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="75" parent="11"> | ||||
|     <key id="82" parent="11"> | ||||
|       <ColNames>noteImageId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_note_images_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="76" parent="12" name="noteRevisionId"> | ||||
|     <column id="83" parent="12" name="noteRevisionId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="77" parent="12" name="noteId"> | ||||
|     <column id="84" parent="12" name="noteId"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="78" parent="12" name="title"> | ||||
|     <column id="85" parent="12" name="title"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|     </column> | ||||
|     <column id="79" parent="12" name="content"> | ||||
|     <column id="86" parent="12" name="content"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|     </column> | ||||
|     <column id="80" parent="12" name="isProtected"> | ||||
|     <column id="87" parent="12" name="isProtected"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="81" parent="12" name="dateModifiedFrom"> | ||||
|     <column id="88" parent="12" name="dateModifiedFrom"> | ||||
|       <Position>6</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="82" parent="12" name="dateModifiedTo"> | ||||
|     <column id="89" parent="12" name="dateModifiedTo"> | ||||
|       <Position>7</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="83" parent="12" name="type"> | ||||
|     <column id="90" parent="12" name="type"> | ||||
|       <Position>8</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>''</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="84" parent="12" name="mime"> | ||||
|     <column id="91" parent="12" name="mime"> | ||||
|       <Position>9</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>''</DefaultExpression> | ||||
|     </column> | ||||
|     <index id="85" parent="12" name="sqlite_autoindex_note_revisions_1"> | ||||
|     <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> | ||||
|       <ColNames>noteRevisionId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="86" parent="12" name="IDX_note_revisions_noteId"> | ||||
|     <index id="94" parent="12" name="IDX_note_revisions_noteId"> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="87" parent="12" name="IDX_note_revisions_dateModifiedFrom"> | ||||
|     <index id="95" parent="12" name="IDX_note_revisions_dateModifiedFrom"> | ||||
|       <ColNames>dateModifiedFrom</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="88" parent="12" name="IDX_note_revisions_dateModifiedTo"> | ||||
|     <index id="96" parent="12" name="IDX_note_revisions_dateModifiedTo"> | ||||
|       <ColNames>dateModifiedTo</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="89" parent="12"> | ||||
|     <key id="97" parent="12"> | ||||
|       <ColNames>noteRevisionId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="90" parent="13" name="noteId"> | ||||
|     <column id="98" parent="13" name="noteId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="91" parent="13" name="title"> | ||||
|     <column id="99" parent="13" name="title"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>"unnamed"</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="92" parent="13" name="content"> | ||||
|     <column id="100" parent="13" name="content"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>""</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="93" parent="13" name="isProtected"> | ||||
|     <column id="101" parent="13" name="isProtected"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="94" parent="13" name="isDeleted"> | ||||
|     <column id="102" parent="13" name="isDeleted"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="95" parent="13" name="dateCreated"> | ||||
|     <column id="103" parent="13" name="dateCreated"> | ||||
|       <Position>6</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="96" parent="13" name="dateModified"> | ||||
|     <column id="104" parent="13" name="dateModified"> | ||||
|       <Position>7</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="97" parent="13" name="type"> | ||||
|     <column id="105" parent="13" name="type"> | ||||
|       <Position>8</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>'text'</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="98" parent="13" name="mime"> | ||||
|     <column id="106" parent="13" name="mime"> | ||||
|       <Position>9</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>'text/html'</DefaultExpression> | ||||
|     </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> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="100" parent="13" name="IDX_notes_isDeleted"> | ||||
|       <ColNames>isDeleted</ColNames> | ||||
|     <index id="109" parent="13" name="IDX_notes_type"> | ||||
|       <ColNames>type</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="101" parent="13"> | ||||
|     <key id="110" parent="13"> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="102" parent="14" name="name"> | ||||
|     <column id="111" parent="14" name="name"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="103" parent="14" name="value"> | ||||
|     <column id="112" parent="14" name="value"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|     </column> | ||||
|     <column id="104" parent="14" name="dateModified"> | ||||
|     <column id="113" parent="14" name="dateModified"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|     </column> | ||||
|     <column id="105" parent="14" name="isSynced"> | ||||
|     <column id="114" parent="14" name="isSynced"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>INTEGER|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </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> | ||||
|       <ColNames>name</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="107" parent="14"> | ||||
|     <key id="118" parent="14"> | ||||
|       <ColNames>name</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="108" parent="15" name="branchId"> | ||||
|     <column id="119" parent="15" name="branchId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="109" parent="15" name="notePath"> | ||||
|     <column id="120" parent="15" name="notePath"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="110" parent="15" name="dateAccessed"> | ||||
|     <column id="121" parent="15" name="dateCreated"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="111" parent="15" name="isDeleted"> | ||||
|     <column id="122" parent="15" name="isDeleted"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|     </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> | ||||
|       <ColNames>branchId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="113" parent="15"> | ||||
|     <key id="125" parent="15"> | ||||
|       <ColNames>branchId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="114" parent="16" name="sourceId"> | ||||
|     <column id="126" parent="16" name="sourceId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="115" parent="16" name="dateCreated"> | ||||
|     <column id="127" parent="16" name="dateCreated"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </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> | ||||
|       <ColNames>sourceId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="117" parent="16"> | ||||
|     <key id="129" parent="16"> | ||||
|       <ColNames>sourceId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="118" parent="17" name="type"> | ||||
|     <column id="130" parent="17" name="type"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>text|0s</DataType> | ||||
|     </column> | ||||
|     <column id="119" parent="17" name="name"> | ||||
|     <column id="131" parent="17" name="name"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>text|0s</DataType> | ||||
|     </column> | ||||
|     <column id="120" parent="17" name="tbl_name"> | ||||
|     <column id="132" parent="17" name="tbl_name"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>text|0s</DataType> | ||||
|     </column> | ||||
|     <column id="121" parent="17" name="rootpage"> | ||||
|     <column id="133" parent="17" name="rootpage"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>integer|0s</DataType> | ||||
|     </column> | ||||
|     <column id="122" parent="17" name="sql"> | ||||
|     <column id="134" parent="17" name="sql"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>text|0s</DataType> | ||||
|     </column> | ||||
|     <column id="123" parent="18" name="name"> | ||||
|     <column id="135" parent="18" name="name"> | ||||
|       <Position>1</Position> | ||||
|     </column> | ||||
|     <column id="124" parent="18" name="seq"> | ||||
|     <column id="136" parent="18" name="seq"> | ||||
|       <Position>2</Position> | ||||
|     </column> | ||||
|     <column id="125" parent="19" name="id"> | ||||
|     <column id="137" parent="19" name="id"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>INTEGER|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <SequenceIdentity>1</SequenceIdentity> | ||||
|     </column> | ||||
|     <column id="126" parent="19" name="entityName"> | ||||
|     <column id="138" parent="19" name="entityName"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="127" parent="19" name="entityId"> | ||||
|     <column id="139" parent="19" name="entityId"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="128" parent="19" name="sourceId"> | ||||
|     <column id="140" parent="19" name="sourceId"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="129" parent="19" name="syncDate"> | ||||
|     <column id="141" parent="19" name="syncDate"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <index id="130" parent="19" name="IDX_sync_entityName_entityId"> | ||||
|     <index id="142" parent="19" name="IDX_sync_entityName_entityId"> | ||||
|       <ColNames>entityName | ||||
| entityId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="131" parent="19" name="IDX_sync_syncDate"> | ||||
|     <index id="143" parent="19" name="IDX_sync_syncDate"> | ||||
|       <ColNames>syncDate</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="132" parent="19"> | ||||
|     <key id="144" parent="19"> | ||||
|       <ColNames>id</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|     </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" ( | ||||
|   `id`	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||
|   `entityName`	TEXT NOT NULL, | ||||
| @@ -29,7 +24,7 @@ CREATE TABLE IF NOT EXISTS "note_revisions" ( | ||||
|   `isProtected`	INT NOT NULL DEFAULT 0, | ||||
|   `dateModifiedFrom` 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` ( | ||||
|   `noteId` | ||||
| ); | ||||
| @@ -49,7 +44,7 @@ CREATE TABLE IF NOT EXISTS "images" | ||||
|   isDeleted INT NOT NULL DEFAULT 0, | ||||
|   dateModified TEXT NOT NULL, | ||||
|   dateCreated TEXT NOT NULL | ||||
| ); | ||||
| , hash TEXT DEFAULT "" NOT NULL); | ||||
| CREATE TABLE note_images | ||||
| ( | ||||
|   noteImageId TEXT PRIMARY KEY NOT NULL, | ||||
| @@ -58,7 +53,7 @@ CREATE TABLE note_images | ||||
|   isDeleted INT NOT NULL DEFAULT 0, | ||||
|   dateModified 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_imageId ON note_images (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, | ||||
|   dateCreated TEXT NOT NULL, | ||||
|   isDeleted INT NOT NULL DEFAULT 0 | ||||
| ); | ||||
| , hash TEXT DEFAULT "" NOT NULL); | ||||
| CREATE TABLE IF NOT EXISTS "branches" ( | ||||
|   `branchId`	TEXT NOT NULL, | ||||
|   `noteId`	TEXT NOT NULL, | ||||
| @@ -77,7 +72,7 @@ CREATE TABLE IF NOT EXISTS "branches" ( | ||||
|   `prefix`	TEXT, | ||||
|   `isExpanded`	BOOLEAN, | ||||
|   `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`) | ||||
| ); | ||||
| CREATE INDEX `IDX_branches_noteId` ON `branches` ( | ||||
| @@ -87,12 +82,6 @@ CREATE INDEX `IDX_branches_noteId_parentNoteId` ON `branches` ( | ||||
|   `noteId`, | ||||
|   `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 | ||||
| ( | ||||
|   labelId  TEXT not null primary key, | ||||
| @@ -103,18 +92,11 @@ CREATE TABLE labels | ||||
|   dateCreated  TEXT not null, | ||||
|   dateModified TEXT not null, | ||||
|   isDeleted    INT  not null | ||||
| ); | ||||
| , hash TEXT DEFAULT "" NOT NULL); | ||||
| CREATE INDEX IDX_labels_name_value | ||||
|   on labels (name, value); | ||||
| CREATE INDEX IDX_labels_noteId | ||||
|   on labels (noteId); | ||||
| CREATE TABLE IF NOT EXISTS "event_log" | ||||
| ( | ||||
|   id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, | ||||
|   noteId TEXT, | ||||
|   comment TEXT, | ||||
|   dateAdded TEXT NOT NULL | ||||
| ); | ||||
| CREATE TABLE IF NOT EXISTS "notes" ( | ||||
|   `noteId`	TEXT NOT NULL, | ||||
|   `title`	TEXT NOT NULL DEFAULT "unnamed", | ||||
| @@ -124,9 +106,31 @@ CREATE TABLE IF NOT EXISTS "notes" ( | ||||
|   `dateCreated`	TEXT NOT NULL, | ||||
|   `dateModified`	TEXT NOT NULL, | ||||
|   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`) | ||||
| ); | ||||
| CREATE INDEX `IDX_notes_isDeleted` ON `notes` ( | ||||
|   `isDeleted` | ||||
| CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId); | ||||
| 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", | ||||
|   "version": "0.12.0", | ||||
|   "version": "0.13.0-beta", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
| @@ -13618,9 +13618,9 @@ | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "xmlbuilder": { | ||||
|           "version": "9.0.4", | ||||
|           "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz", | ||||
|           "integrity": "sha1-UZy0ymhtAFqEINNJbz8MruzKWA8=" | ||||
|           "version": "9.0.7", | ||||
|           "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", | ||||
|           "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "name": "trilium", | ||||
|   "description": "Trilium Notes", | ||||
|   "version": "0.13.0-beta", | ||||
|   "version": "0.14.1", | ||||
|   "license": "AGPL-3.0-only", | ||||
|   "main": "electron.js", | ||||
|   "repository": { | ||||
| @@ -59,7 +59,8 @@ | ||||
|     "sqlite": "^2.9.2", | ||||
|     "tar-stream": "^1.6.1", | ||||
|     "unescape": "^1.0.1", | ||||
|     "ws": "^5.2.0" | ||||
|     "ws": "^5.2.0", | ||||
|     "xml2js": "^0.4.19" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "electron": "^2.0.1", | ||||
|   | ||||
| @@ -27,7 +27,11 @@ class Branch extends Entity { | ||||
|             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() { | ||||
|         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() { | ||||
|   | ||||
| @@ -5,8 +5,8 @@ const dateUtils = require('../services/date_utils'); | ||||
|  | ||||
| class Option extends Entity { | ||||
|     static get tableName() { return "options"; } | ||||
|     static get primaryKeyName() { return "name"; } | ||||
|     static get hashedProperties() { return ["name", "value"]; } | ||||
|     static get primaryKeyName() { return "optionId"; } | ||||
|     static get hashedProperties() { return ["optionId", "name", "value"]; } | ||||
|  | ||||
|     beforeSaving() { | ||||
|         super.beforeSaving(); | ||||
|   | ||||
| @@ -1,11 +1,24 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const Entity = require('./entity'); | ||||
| const dateUtils = require('../services/date_utils'); | ||||
|  | ||||
| class RecentNote extends Entity { | ||||
|     static get tableName() { return "recent_notes"; } | ||||
|     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; | ||||
							
								
								
									
										
											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 noteDetailService from '../services/note_detail.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 $form = $("#add-link-form"); | ||||
| @@ -11,6 +12,7 @@ const $linkTitle = $("#link-title"); | ||||
| const $clonePrefix = $("#clone-prefix"); | ||||
| const $linkTitleFormGroup = $("#add-link-title-form-group"); | ||||
| const $prefixFormGroup = $("#add-link-prefix-form-group"); | ||||
| const $linkTypeDiv = $("#add-link-type-div"); | ||||
| const $linkTypes = $("input[name='add-link-type']"); | ||||
| const $linkTypeHtml = $linkTypes.filter('input[value="html"]'); | ||||
|  | ||||
| @@ -52,8 +54,12 @@ async function showDialog() { | ||||
|     } | ||||
|  | ||||
|     $autoComplete.autocomplete({ | ||||
|         source: await autocompleteService.getAutocompleteItems(), | ||||
|         minLength: 0, | ||||
|         source: async function(request, response) { | ||||
|             const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term)); | ||||
|  | ||||
|             response(result); | ||||
|         }, | ||||
|         minLength: 2, | ||||
|         change: async () => { | ||||
|             const val = $autoComplete.val(); | ||||
|             const notePath = linkService.getNodePathFromLabel(val); | ||||
| @@ -92,7 +98,16 @@ $form.submit(() => { | ||||
|  | ||||
|             $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') { | ||||
|             const prefix = $clonePrefix.val(); | ||||
| @@ -113,17 +128,21 @@ $form.submit(() => { | ||||
|     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() { | ||||
|     const value = $linkTypes.filter(":checked").val(); | ||||
|  | ||||
|     if (value === 'html') { | ||||
|         $linkTitleFormGroup.show(); | ||||
|         $prefixFormGroup.hide(); | ||||
|     } | ||||
|     else { | ||||
|         $linkTitleFormGroup.hide(); | ||||
|         $prefixFormGroup.show(); | ||||
|     } | ||||
|     $linkTitleFormGroup.toggle(!hasSelection() && value === 'html'); | ||||
|     $prefixFormGroup.toggle(!hasSelection() && value !== 'html'); | ||||
|  | ||||
|     $linkTypeDiv.toggle(!hasSelection()); | ||||
| } | ||||
|  | ||||
| $linkTypes.change(linkTypeChanged); | ||||
|   | ||||
| @@ -19,7 +19,7 @@ async function showDialog() { | ||||
|     $list.html(''); | ||||
|  | ||||
|     for (const event of result) { | ||||
|         const dateTime = utils.formatDateTime(utils.parseDate(event.dateAdded)); | ||||
|         const dateTime = utils.formatDateTime(utils.parseDate(event.dateCreated)); | ||||
|  | ||||
|         if (event.noteId) { | ||||
|             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 | ||||
| window.glob.getCurrentNode = treeService.getCurrentNode; | ||||
| window.glob.getHeaders = server.getHeaders; | ||||
| window.glob.showAddLinkDialog = addLinkDialog.showDialog; | ||||
|  | ||||
| // required for ESLint plugin | ||||
| 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 after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"}, | ||||
|         {title: "----"}, | ||||
|         {title: "Export branch", cmd: "exportBranch", uiIcon: " ui-icon-arrowthick-1-ne"}, | ||||
|         {title: "Import into branch", cmd: "importBranch", uiIcon: "ui-icon-arrowthick-1-sw"}, | ||||
|         {title: "Export branch", cmd: "exportBranch", uiIcon: " ui-icon-arrowthick-1-ne", children: [ | ||||
|             {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: "Collapse branch <kbd>Alt+-</kbd>", cmd: "collapseBranch", uiIcon: "ui-icon-minus"}, | ||||
|         {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"} | ||||
|  | ||||
|     ], | ||||
|     beforeOpen: async (event, ui) => { | ||||
|         const node = $.ui.fancytree.getNode(ui.target); | ||||
|         const branch = await treeCache.getBranch(node.data.branchId); | ||||
|         const note = await treeCache.getNote(node.data.noteId); | ||||
|         const parentNote = await treeCache.getNote(branch.parentNoteId); | ||||
|         const isNotRoot = note.noteId !== 'root'; | ||||
|  | ||||
|         // Modify menu entries depending on node status | ||||
|         $tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0 && (!parentNote || parentNote.type !== 'search')); | ||||
|         $tree.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0 && note.type !== 'search'); | ||||
|         $tree.contextmenu("enableEntry", "insertNoteHere", !parentNote || parentNote.type !== 'search'); | ||||
|         $tree.contextmenu("enableEntry", "insertNoteHere", isNotRoot && parentNote.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", "exportBranch", note.type !== 'search'); | ||||
|  | ||||
| @@ -159,8 +165,11 @@ const contextMenuOptions = { | ||||
|         else if (ui.cmd === "delete") { | ||||
|             treeChangesService.deleteNodes(treeService.getSelectedNodes(true)); | ||||
|         } | ||||
|         else if (ui.cmd === "exportBranch") { | ||||
|             exportService.exportBranch(node.data.noteId); | ||||
|         else if (ui.cmd === "exportBranchToTar") { | ||||
|             exportService.exportBranch(node.data.noteId, 'tar'); | ||||
|         } | ||||
|         else if (ui.cmd === "exportBranchToOpml") { | ||||
|             exportService.exportBranch(node.data.noteId, 'opml'); | ||||
|         } | ||||
|         else if (ui.cmd === "importBranch") { | ||||
|             exportService.importBranch(node.data.noteId); | ||||
|   | ||||
| @@ -12,6 +12,7 @@ import recentChangesDialog from "../dialogs/recent_changes.js"; | ||||
| import sqlConsoleDialog from "../dialogs/sql_console.js"; | ||||
| import searchTreeService from "./search_tree.js"; | ||||
| import labelsDialog from "../dialogs/labels.js"; | ||||
| import protectedSessionService from "./protected_session.js"; | ||||
|  | ||||
| function registerEntrypoints() { | ||||
|     // hot keys are active also inside inputs and content editables | ||||
| @@ -31,6 +32,9 @@ function registerEntrypoints() { | ||||
|  | ||||
|     $("#recent-changes-button").click(recentChangesDialog.showDialog); | ||||
|  | ||||
|     $("#protected-session-on").click(protectedSessionService.enterProtectedSession); | ||||
|     $("#protected-session-off").click(protectedSessionService.leaveProtectedSession); | ||||
|  | ||||
|     $("#recent-notes-button").click(recentNotesDialog.showDialog); | ||||
|     utils.bindShortcut('ctrl+e', recentNotesDialog.showDialog); | ||||
|  | ||||
| @@ -45,6 +49,10 @@ function registerEntrypoints() { | ||||
|     utils.bindShortcut('alt+o', sqlConsoleDialog.showDialog); | ||||
|  | ||||
|     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+right', window.history.forward); | ||||
|     } | ||||
|   | ||||
| @@ -3,9 +3,9 @@ import protectedSessionHolder from './protected_session_holder.js'; | ||||
| import utils from './utils.js'; | ||||
| import server from './server.js'; | ||||
|  | ||||
| function exportBranch(noteId) { | ||||
|     const url = utils.getHost() + "/api/notes/" + noteId + "/export?protectedSessionId=" | ||||
|         + encodeURIComponent(protectedSessionHolder.getProtectedSessionId()); | ||||
| function exportBranch(noteId, format) { | ||||
|     const url = utils.getHost() + "/api/notes/" + noteId + "/export/" + format + | ||||
|         "?protectedSessionId=" + encodeURIComponent(protectedSessionHolder.getProtectedSessionId()); | ||||
|  | ||||
|     utils.download(url); | ||||
| } | ||||
| @@ -29,7 +29,7 @@ $("#import-upload").change(async function() { | ||||
|         type: 'POST', | ||||
|         contentType: 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(); | ||||
| }); | ||||
|   | ||||
| @@ -64,24 +64,27 @@ function focus() { | ||||
| } | ||||
|  | ||||
| async function executeCurrentNote() { | ||||
|     if (noteDetailService.getCurrentNoteType() === 'code') { | ||||
|         // make sure note is saved so we load latest changes | ||||
|         await noteDetailService.saveNoteIfChanged(); | ||||
|  | ||||
|         const currentNote = noteDetailService.getCurrentNote(); | ||||
|  | ||||
|         if (currentNote.mime.endsWith("env=frontend")) { | ||||
|             const bundle = await server.get('script/bundle/' + noteDetailService.getCurrentNoteId()); | ||||
|  | ||||
|             bundleService.executeBundle(bundle); | ||||
|         } | ||||
|  | ||||
|         if (currentNote.mime.endsWith("env=backend")) { | ||||
|             await server.post('script/run/' + noteDetailService.getCurrentNoteId()); | ||||
|         } | ||||
|  | ||||
|         infoService.showMessage("Note executed"); | ||||
|     // 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 | ||||
|     await noteDetailService.saveNoteIfChanged(); | ||||
|  | ||||
|     const currentNote = noteDetailService.getCurrentNote(); | ||||
|  | ||||
|     if (currentNote.mime.endsWith("env=frontend")) { | ||||
|         const bundle = await server.get('script/bundle/' + noteDetailService.getCurrentNoteId()); | ||||
|  | ||||
|         bundleService.executeBundle(bundle); | ||||
|     } | ||||
|  | ||||
|     if (currentNote.mime.endsWith("env=backend")) { | ||||
|         await server.post('script/run/' + noteDetailService.getCurrentNoteId()); | ||||
|     } | ||||
|  | ||||
|     infoService.showMessage("Note executed"); | ||||
| } | ||||
|  | ||||
| $(document).bind('keydown', "ctrl+return", executeCurrentNote); | ||||
|   | ||||
| @@ -1,21 +1,73 @@ | ||||
| import bundleService from "./bundle.js"; | ||||
| import server from "./server.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 $toggleEditButton = $('#toggle-edit-button'); | ||||
| const $renderButton = $('#render-button'); | ||||
|  | ||||
| let codeEditorInitialized; | ||||
|  | ||||
| async function show() { | ||||
|     codeEditorInitialized = false; | ||||
|  | ||||
|     $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()); | ||||
|  | ||||
|     $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); | ||||
| } | ||||
|  | ||||
| $(document).bind('keydown', "ctrl+return", render); | ||||
|  | ||||
| export default { | ||||
|     show, | ||||
|     getContent: () => null, | ||||
|     getContent: noteDetailCodeService.getContent, | ||||
|     focus: () => null | ||||
| } | ||||
| @@ -4,6 +4,9 @@ import server from './server.js'; | ||||
| import infoService from "./info.js"; | ||||
|  | ||||
| const $executeScriptButton = $("#execute-script-button"); | ||||
| const $toggleEditButton = $('#toggle-edit-button'); | ||||
| const $renderButton = $('#render-button'); | ||||
|  | ||||
| const noteTypeModel = new NoteTypeModel(); | ||||
|  | ||||
| function NoteTypeModel() { | ||||
| @@ -107,7 +110,7 @@ function NoteTypeModel() { | ||||
|  | ||||
|     this.selectRender = function() { | ||||
|         self.type('render'); | ||||
|         self.mime(''); | ||||
|         self.mime('text/html'); | ||||
|  | ||||
|         save(); | ||||
|     }; | ||||
| @@ -128,6 +131,9 @@ function NoteTypeModel() { | ||||
|  | ||||
|     this.updateExecuteScriptButtonVisibility = function() { | ||||
|         $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 $protectButton = $("#protect-button"); | ||||
| const $unprotectButton = $("#unprotect-button"); | ||||
| const $protectedSessionOnButton = $("#protected-session-on"); | ||||
| const $protectedSessionOffButton = $("#protected-session-off"); | ||||
|  | ||||
| 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) { | ||||
|     const dfd = $.Deferred(); | ||||
|  | ||||
| @@ -46,7 +60,7 @@ async function setupProtectedSession() { | ||||
|     const password = $password.val(); | ||||
|     $password.val(""); | ||||
|  | ||||
|     const response = await enterProtectedSession(password); | ||||
|     const response = await enterProtectedSessionOnServer(password); | ||||
|  | ||||
|     if (!response.success) { | ||||
|         infoService.showError("Wrong password."); | ||||
| @@ -67,6 +81,9 @@ async function setupProtectedSession() { | ||||
|  | ||||
|         protectedSessionDeferred.resolve(); | ||||
|  | ||||
|         $protectedSessionOnButton.addClass('active'); | ||||
|         $protectedSessionOffButton.removeClass('active'); | ||||
|  | ||||
|         protectedSessionDeferred = null; | ||||
|     } | ||||
| } | ||||
| @@ -81,7 +98,7 @@ function ensureDialogIsClosed() { | ||||
|     $password.val(''); | ||||
| } | ||||
|  | ||||
| async function enterProtectedSession(password) { | ||||
| async function enterProtectedSessionOnServer(password) { | ||||
|     return await server.post('login/protected', { | ||||
|         password: password | ||||
|     }); | ||||
| @@ -138,5 +155,7 @@ export default { | ||||
|     protectNoteAndSendToServer, | ||||
|     unprotectNoteAndSendToServer, | ||||
|     protectBranch, | ||||
|     ensureDialogIsClosed | ||||
|     ensureDialogIsClosed, | ||||
|     enterProtectedSession, | ||||
|     leaveProtectedSession | ||||
| }; | ||||
| @@ -17,11 +17,11 @@ import Branch from '../entities/branch.js'; | ||||
| import NoteShort from '../entities/note_short.js'; | ||||
|  | ||||
| const $tree = $("#tree"); | ||||
| const $parentList = $("#parent-list"); | ||||
| const $parentListList = $("#parent-list-inner"); | ||||
| const $createTopLevelNoteButton = $("#create-top-level-note-button"); | ||||
| const $collapseTreeButton = $("#collapse-tree-button"); | ||||
| const $scrollToCurrentNoteButton = $("#scroll-to-current-note-button"); | ||||
| const $notePathList = $("#note-path-list"); | ||||
| const $notePathCount = $("#note-path-count"); | ||||
|  | ||||
| let startNotePath = null; | ||||
|  | ||||
| @@ -81,11 +81,15 @@ async function expandToNote(notePath, expandOpts) { | ||||
|  | ||||
|     const noteId = treeUtils.getNoteIdFromNotePath(notePath); | ||||
|  | ||||
|     let parentNoteId = 'root'; | ||||
|     let parentNoteId = 'none'; | ||||
|  | ||||
|     for (const childNoteId of runPath) { | ||||
|         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) { | ||||
|             return node; | ||||
|         } | ||||
| @@ -115,7 +119,10 @@ async function getRunPath(notePath) { | ||||
|     utils.assertArguments(notePath); | ||||
|  | ||||
|     const path = notePath.split("/").reverse(); | ||||
|     path.push('root'); | ||||
|  | ||||
|     if (!path.includes("root")) { | ||||
|         path.push('root'); | ||||
|     } | ||||
|  | ||||
|     const effectivePath = []; | ||||
|     let childNoteId = null; | ||||
| @@ -151,6 +158,8 @@ async function getRunPath(notePath) { | ||||
|                         for (const noteId of pathToRoot) { | ||||
|                             effectivePath.push(noteId); | ||||
|                         } | ||||
|  | ||||
|                         effectivePath.push('root'); | ||||
|                     } | ||||
|  | ||||
|                     break; | ||||
| @@ -162,7 +171,7 @@ async function getRunPath(notePath) { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (parentNoteId === 'root') { | ||||
|         if (parentNoteId === 'none') { | ||||
|             break; | ||||
|         } | ||||
|         else { | ||||
| @@ -180,16 +189,13 @@ async function showParentList(noteId, node) { | ||||
|     const note = await treeCache.getNote(noteId); | ||||
|     const parents = await note.getParentNotes(); | ||||
|  | ||||
|     if (!parents.length) { | ||||
|         infoService.throwError("Can't find parents for noteId=" + noteId); | ||||
|     } | ||||
|     $notePathCount.html(parents.length + " path" + (parents.length > 0 ? "s" : "")); | ||||
|  | ||||
|     if (parents.length <= 1) { | ||||
|         $parentList.hide(); | ||||
|     } | ||||
|     else { | ||||
|         $parentList.show(); | ||||
|         $parentListList.empty(); | ||||
|         //$notePathList.show(); | ||||
|         $notePathList.empty(); | ||||
|  | ||||
|         for (const parentNote of parents) { | ||||
|             const parentNotePath = await getSomeNotePath(parentNote); | ||||
| @@ -197,16 +203,13 @@ async function showParentList(noteId, node) { | ||||
|             const notePath = parentNotePath ? (parentNotePath + '/' + noteId) : noteId; | ||||
|             const title = await treeUtils.getNotePathTitle(notePath); | ||||
|  | ||||
|             let item; | ||||
|             const item = $("<li/>").append(await linkService.createNoteLink(notePath, title)); | ||||
|  | ||||
|             if (node.getParent().data.noteId === parentNote.noteId) { | ||||
|                 item = $("<span/>").attr("title", "Current note").append(title); | ||||
|             } | ||||
|             else { | ||||
|                 item = await linkService.createNoteLink(notePath, title); | ||||
|                 item.addClass("current"); | ||||
|             } | ||||
|  | ||||
|             $parentListList.append($("<li/>").append(item)); | ||||
|             $notePathList.append(item); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -294,6 +297,7 @@ function initFancyTree(tree) { | ||||
|         extensions: ["hotkeys", "filter", "dnd", "clones"], | ||||
|         source: tree, | ||||
|         scrollParent: $tree, | ||||
|         minExpandLevel: 2, // root can't be collapsed | ||||
|         click: (event, data) => { | ||||
|             const targetType = data.targetType; | ||||
|             const node = data.node; | ||||
|   | ||||
| @@ -10,7 +10,7 @@ async function prepareTree(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) { | ||||
| @@ -22,6 +22,35 @@ async function prepareBranch(note) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function prepareNode(branch) { | ||||
|     const note = await branch.getNote(); | ||||
|     const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title; | ||||
|  | ||||
|     const node = { | ||||
|         noteId: note.noteId, | ||||
|         parentNoteId: branch.parentNoteId, | ||||
|         branchId: branch.branchId, | ||||
|         isProtected: note.isProtected, | ||||
|         title: utils.escapeHtml(title), | ||||
|         extraClasses: await getExtraClasses(note), | ||||
|         refKey: note.noteId, | ||||
|         expanded: note.type !== 'search' && branch.isExpanded | ||||
|     }; | ||||
|  | ||||
|     if (note.hasChildren() || note.type === 'search') { | ||||
|         node.folder = true; | ||||
|  | ||||
|         if (node.expanded && note.type !== 'search') { | ||||
|             node.children = await prepareRealBranch(note); | ||||
|         } | ||||
|         else { | ||||
|             node.lazy = true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return node; | ||||
| } | ||||
|  | ||||
| async function prepareRealBranch(parentNote) { | ||||
|     utils.assertArguments(parentNote); | ||||
|  | ||||
| @@ -35,30 +64,7 @@ async function prepareRealBranch(parentNote) { | ||||
|     const noteList = []; | ||||
|  | ||||
|     for (const branch of childBranches) { | ||||
|         const note = await branch.getNote(); | ||||
|         const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title; | ||||
|  | ||||
|         const node = { | ||||
|             noteId: note.noteId, | ||||
|             parentNoteId: branch.parentNoteId, | ||||
|             branchId: branch.branchId, | ||||
|             isProtected: note.isProtected, | ||||
|             title: utils.escapeHtml(title), | ||||
|             extraClasses: await getExtraClasses(note), | ||||
|             refKey: note.noteId, | ||||
|             expanded: note.type !== 'search' && branch.isExpanded | ||||
|         }; | ||||
|  | ||||
|         if (note.hasChildren() || note.type === 'search') { | ||||
|             node.folder = true; | ||||
|  | ||||
|             if (node.expanded && note.type !== 'search') { | ||||
|                 node.children = await prepareRealBranch(note); | ||||
|             } | ||||
|             else { | ||||
|                 node.lazy = true; | ||||
|             } | ||||
|         } | ||||
|         const node = await prepareNode(branch); | ||||
|  | ||||
|         noteList.push(node); | ||||
|     } | ||||
| @@ -90,6 +96,10 @@ async function getExtraClasses(note) { | ||||
|  | ||||
|     const extraClasses = []; | ||||
|  | ||||
|     if (note.noteId === 'root') { | ||||
|         extraClasses.push("tree-root"); | ||||
|     } | ||||
|  | ||||
|     if (note.isProtected) { | ||||
|         extraClasses.push("protected"); | ||||
|     } | ||||
|   | ||||
| @@ -58,6 +58,10 @@ class TreeCache { | ||||
|  | ||||
|     /** @return NoteShort */ | ||||
|     async getNote(noteId) { | ||||
|         if (noteId === 'none') { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         return (await this.getNotes([noteId]))[0]; | ||||
|     } | ||||
|  | ||||
| @@ -68,6 +72,10 @@ class TreeCache { | ||||
|     } | ||||
|  | ||||
|     addBranchRelationship(branchId, childNoteId, parentNoteId) { | ||||
|         if (parentNoteId === 'none') { // applies only to root element | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.childParentToBranch[childNoteId + '-' + parentNoteId] = branchId; | ||||
|  | ||||
|         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; | ||||
| } | ||||
|  | ||||
| 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 */ | ||||
| span.fancytree-active:not(.fancytree-focused) .fancytree-title { | ||||
|     background-color: #ddd !important; | ||||
| @@ -166,20 +175,6 @@ div.ui-tooltip { | ||||
|     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 | ||||
| * <webview> element for search window. | ||||
| @@ -240,7 +235,7 @@ div.ui-tooltip { | ||||
|     filter: opacity(7%); | ||||
| } | ||||
|  | ||||
| .dropdown-menu li:not(.divider) { | ||||
| #note-type .dropdown-menu li:not(.divider) { | ||||
|     padding: 5px; | ||||
|     width: 200px; | ||||
| } | ||||
| @@ -268,10 +263,20 @@ div.ui-tooltip { | ||||
|  | ||||
| #note-detail-code { | ||||
|     min-height: 200px; | ||||
|     overflow: auto; | ||||
| } | ||||
|  | ||||
| #note-detail-render { | ||||
|     min-height: 200px; | ||||
| } | ||||
|  | ||||
| .CodeMirror { | ||||
|     font-family: "Liberation Mono", "Lucida Console", monospace; | ||||
|     height: auto; | ||||
| } | ||||
|  | ||||
| .CodeMirror-scroll { | ||||
|     min-height: 200px; | ||||
| } | ||||
|  | ||||
| #note-id-display { | ||||
| @@ -347,4 +352,21 @@ div.ui-tooltip { | ||||
|  | ||||
| #sql-console-query .CodeMirror { | ||||
|     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'); | ||||
|  | ||||
| async function getEventLog() { | ||||
|     await deleteOld(); | ||||
|  | ||||
|     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]); | ||||
|     } | ||||
|     return await sql.getRows("SELECT * FROM event_log ORDER BY dateCreated DESC"); | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   | ||||
| @@ -5,12 +5,85 @@ const html = require('html'); | ||||
| const tar = require('tar-stream'); | ||||
| const sanitize = require("sanitize-filename"); | ||||
| const repository = require("../../services/repository"); | ||||
| const utils = require('../../services/utils'); | ||||
|  | ||||
| async function exportNote(req, res) { | ||||
|     const noteId = req.params.noteId; | ||||
|     const format = req.params.format; | ||||
|  | ||||
|     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 exportedNoteIds = []; | ||||
|   | ||||
| @@ -7,6 +7,79 @@ const Branch = require('../../entities/branch'); | ||||
| const tar = require('tar-stream'); | ||||
| const stream = require('stream'); | ||||
| 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) { | ||||
|     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) { | ||||
|     for (const file of files) { | ||||
|         if (file.meta.version !== 1) { | ||||
| @@ -143,5 +198,5 @@ async function importNotes(files, parentNoteId, noteIdMap) { | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     importTar | ||||
|     importToBranch | ||||
| }; | ||||
| @@ -16,7 +16,7 @@ async function getRecentNotes() { | ||||
|         recent_notes.isDeleted = 0 | ||||
|         AND branches.isDeleted = 0 | ||||
|       ORDER BY  | ||||
|         dateAccessed DESC | ||||
|         dateCreated DESC | ||||
|       LIMIT 200`); | ||||
| } | ||||
|  | ||||
| @@ -26,9 +26,7 @@ async function addRecentNote(req) { | ||||
|  | ||||
|     await new RecentNote({ | ||||
|         branchId: branchId, | ||||
|         notePath: notePath, | ||||
|         dateAccessed: dateUtils.nowDate(), | ||||
|         isDeleted: 0 | ||||
|         notePath: notePath | ||||
|     }).save(); | ||||
|  | ||||
|     await optionService.setOption('startNotePath', notePath); | ||||
|   | ||||
| @@ -5,11 +5,9 @@ const optionService = require('../../services/options'); | ||||
| const protectedSessionService = require('../../services/protected_session'); | ||||
|  | ||||
| async function getNotes(noteIds) { | ||||
|     const questionMarks = noteIds.map(() => "?").join(","); | ||||
|  | ||||
|     const notes = await sql.getRows(` | ||||
|     const notes = await sql.getManyRows(` | ||||
|       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); | ||||
|  | ||||
| @@ -18,11 +16,11 @@ async function getNotes(noteIds) { | ||||
| } | ||||
|  | ||||
| async function getRelations(noteIds) { | ||||
|     const questionMarks = noteIds.map(() => "?").join(","); | ||||
|     const doubledNoteIds = noteIds.concat(noteIds); | ||||
|     // we need to fetch both parentNoteId and noteId matches because we can have loaded child | ||||
|     // 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  | ||||
|          AND (parentNoteId IN (${questionMarks}) OR noteId IN (${questionMarks}))`, doubledNoteIds); | ||||
|     return await sql.getManyRows(`SELECT branchId, noteId AS 'childNoteId', parentNoteId FROM branches WHERE isDeleted = 0  | ||||
|          AND (parentNoteId IN (???) OR noteId IN (???))`, noteIds); | ||||
| } | ||||
|  | ||||
| async function getTree() { | ||||
| @@ -58,12 +56,11 @@ async function load(req) { | ||||
|     const branchIds = req.body.branchIds; | ||||
|  | ||||
|     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.getRows(`SELECT * FROM branches WHERE isDeleted = 0 AND noteId IN (${questionMarks})`, noteIds); | ||||
|     const branches = await sql.getManyRows(`SELECT * FROM branches WHERE isDeleted = 0 AND noteId IN (???)`, 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-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); | ||||
|  | ||||
|     route(GET, '/api/notes/:noteId/export', [auth.checkApiAuthOrElectron], exportRoute.exportNote); | ||||
|     route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importTar, apiResultHandler); | ||||
|     route(GET, '/api/notes/:noteId/export/:format', [auth.checkApiAuthOrElectron], exportRoute.exportNote); | ||||
|     route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importToBranch, apiResultHandler); | ||||
|  | ||||
|     route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware], | ||||
|         filesRoute.uploadFile, apiResultHandler); | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| const build = require('./build'); | ||||
| const packageJson = require('../../package'); | ||||
|  | ||||
| const APP_DB_VERSION = 93; | ||||
| const APP_DB_VERSION = 96; | ||||
|  | ||||
| module.exports = { | ||||
|     appVersion: packageJson.version, | ||||
|   | ||||
| @@ -3,6 +3,7 @@ const sqlInit = require('./sql_init'); | ||||
| const eventService = require('./events'); | ||||
| const repository = require('./repository'); | ||||
| const protectedSessionService = require('./protected_session'); | ||||
| const utils = require('./utils'); | ||||
|  | ||||
| let noteTitles; | ||||
| let protectedNoteTitles; | ||||
| @@ -14,10 +15,10 @@ const hideInAutocomplete = {}; | ||||
| let prefixes = {}; | ||||
|  | ||||
| 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); | ||||
|  | ||||
|     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`); | ||||
|  | ||||
| @@ -58,7 +59,11 @@ function getResults(query) { | ||||
|         } | ||||
|  | ||||
|         for (const parentNoteId of parents) { | ||||
|             const title = getNoteTitle(noteId, parentNoteId); | ||||
|             if (hideInAutocomplete[parentNoteId]) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             const title = getNoteTitle(noteId, parentNoteId).toLowerCase(); | ||||
|             const foundTokens = []; | ||||
|  | ||||
|             for (const token of tokens) { | ||||
| @@ -109,6 +114,7 @@ function search(noteId, tokens, path, results) { | ||||
|         if (parentNoteId === 'root' || hideInAutocomplete[parentNoteId]) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         const title = getNoteTitle(noteId, parentNoteId); | ||||
|         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 = { | ||||
|     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'); | ||||
|  | ||||
| async function getHash(entityConstructor, whereBranch) { | ||||
|     let contentToHash = await sql.getValue(`SELECT GROUP_CONCAT(hash) FROM ${entityConstructor.tableName} ` | ||||
|                 + (whereBranch ? `WHERE ${whereBranch} ` : '') + `ORDER BY ${entityConstructor.primaryKeyName}`); | ||||
|     // subselect is necessary to have correct ordering in GROUP_CONCAT | ||||
|     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 | ||||
|         contentToHash = ""; | ||||
| @@ -56,7 +59,7 @@ async function checkContentHashes(otherHashes) { | ||||
|         if (hashes[key] !== otherHashes[key]) { | ||||
|             allChecksPassed = false; | ||||
|  | ||||
|             await eventLogService.addEvent(`Content hash check for ${key} FAILED. Local is ${hashes[key]}, remote is ${resp.hashes[key]}`); | ||||
|             await eventLogService.addEvent(`Content hash check for ${key} FAILED. Local is ${hashes[key]}, remote is ${otherHashes[key]}`); | ||||
|  | ||||
|             if (key !== 'recent_notes') { | ||||
|                 // 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 dateUtils = require('./date_utils'); | ||||
| const utils = require('./utils'); | ||||
| const log = require('./log'); | ||||
|  | ||||
| async function addEvent(comment) { | ||||
| @@ -8,9 +9,10 @@ async function addEvent(comment) { | ||||
|  | ||||
| async function addNoteEvent(noteId, comment) { | ||||
|     await sql.insert('event_log', { | ||||
|        noteId : noteId, | ||||
|        comment: comment, | ||||
|        dateAdded: dateUtils.nowDate() | ||||
|         eventId: utils.newEntityId(), | ||||
|         noteId : noteId, | ||||
|         comment: comment, | ||||
|         dateCreated: dateUtils.nowDate() | ||||
|     }); | ||||
|  | ||||
|     log.info("Event log for " + noteId + ": " + comment); | ||||
|   | ||||
| @@ -73,7 +73,7 @@ function getParams(params) { | ||||
| } | ||||
|  | ||||
| async function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = []) { | ||||
|     if (!note.isJavaScript() && !note.isHtml() && note.type !== 'render') { | ||||
|     if (!note.isJavaScript() && !note.isHtml()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -62,6 +62,26 @@ async function getValue(query, params = []) { | ||||
|     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 = []) { | ||||
|     return await wrap(async db => db.all(query, ...params)); | ||||
| } | ||||
| @@ -179,6 +199,7 @@ module.exports = { | ||||
|     getRow, | ||||
|     getRowOrNull, | ||||
|     getRows, | ||||
|     getManyRows, | ||||
|     getMap, | ||||
|     getColumn, | ||||
|     execute, | ||||
|   | ||||
| @@ -94,6 +94,12 @@ async function isDbUpToDate() { | ||||
| } | ||||
|  | ||||
| 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'"); | ||||
|  | ||||
|     return !!username; | ||||
|   | ||||
| @@ -207,13 +207,12 @@ const primaryKeys = { | ||||
|     "notes": "noteId", | ||||
|     "branches": "branchId", | ||||
|     "note_revisions": "noteRevisionId", | ||||
|     "option": "name", | ||||
|     "recent_notes": "branchId", | ||||
|     "images": "imageId", | ||||
|     "note_images": "noteImageId", | ||||
|     "labels": "labelId", | ||||
|     "api_tokens": "apiTokenId", | ||||
|     "options": "name" | ||||
|     "options": "optionId" | ||||
| }; | ||||
|  | ||||
| async function getEntityRow(entityName, entityId) { | ||||
|   | ||||
| @@ -127,7 +127,7 @@ async function updateOptions(entity, sourceId) { | ||||
| async function updateRecentNotes(entity, sourceId) { | ||||
|     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.replace('recent_notes', entity); | ||||
|  | ||||
|   | ||||
| @@ -75,6 +75,10 @@ function toObject(array, fn) { | ||||
|     return obj; | ||||
| } | ||||
|  | ||||
| function stripTags(text) { | ||||
|     return text.replace(/<(?:.|\n)*?>/gm, ''); | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     randomSecureToken, | ||||
|     randomString, | ||||
| @@ -88,5 +92,6 @@ module.exports = { | ||||
|     sanitizeSql, | ||||
|     stopWatch, | ||||
|     unescapeHtml, | ||||
|     toObject | ||||
|     toObject, | ||||
|     stripTags | ||||
| }; | ||||
| @@ -13,10 +13,28 @@ | ||||
|           Trilium Notes | ||||
|         </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="recent-notes-button" title="CTRL+E">Recent notes</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 id="plugin-buttons"> | ||||
| @@ -69,16 +87,24 @@ | ||||
|         </div> | ||||
|  | ||||
|         <div id="tree"></div> | ||||
|  | ||||
|         <div id="parent-list"> | ||||
|           <p><strong>Note locations:</strong></p> | ||||
|  | ||||
|           <ul id="parent-list-inner"></ul> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <div style="grid-area: title;"> | ||||
|         <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" | ||||
|              class="icon-action" | ||||
|              id="protect-button" | ||||
| @@ -89,11 +115,15 @@ | ||||
|              id="unprotect-button" | ||||
|              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" | ||||
|                   style="display: none; margin-right: 10px" | ||||
| @@ -104,7 +134,7 @@ | ||||
|               Type: <span data-bind="text: typeString()"></span> | ||||
|               <span class="caret"></span> | ||||
|             </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 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> | ||||
| @@ -121,7 +151,7 @@ | ||||
|               Note actions | ||||
|               <span class="caret"></span> | ||||
|             </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 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> | ||||
| @@ -220,7 +250,7 @@ | ||||
|  | ||||
|     <div id="add-link-dialog" title="Add link" style="display: none;"> | ||||
|       <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"> | ||||
|             <input type="radio" name="add-link-type" value="html"/> | ||||
|             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 messagingService = require('./services/messaging'); | ||||
| const utils = require('./services/utils'); | ||||
| const sqlInit = require('./services/sql_init.js'); | ||||
|  | ||||
| const port = normalizePort(config['Network']['port'] || '3000'); | ||||
| app.set('port', port); | ||||
| @@ -54,7 +55,7 @@ httpServer.listen(port); | ||||
| httpServer.on('error', onError); | ||||
| httpServer.on('listening', onListening); | ||||
|  | ||||
| messagingService.init(httpServer, sessionParser); | ||||
| sqlInit.dbReady.then(() => messagingService.init(httpServer, sessionParser)); | ||||
|  | ||||
| if (utils.isElectron()) { | ||||
|     const electronRouting = require('./routes/electron'); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user