Compare commits
	
		
			130 Commits
		
	
	
		
			v0.10.0-be
			...
			v0.16.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 7a9542b4fc | ||
|  | 3a95c9e1bc | ||
|  | 3d2ef6be01 | ||
|  | d67246699a | ||
|  | 14c704d6db | ||
|  | 4c8eeb2e6f | ||
|  | c1b245c8b1 | ||
|  | 74202d67bb | ||
|  | 26066f39b1 | ||
|  | b255cf190c | ||
|  | bc77b143b0 | ||
|  | 9f0ff6ae7a | ||
|  | 736704c7d6 | ||
|  | 654c116c58 | ||
|  | 89a5cab98f | ||
|  | c39d0be8cd | ||
|  | e75b4cd848 | ||
|  | 378e8f35e5 | ||
|  | bdb5e2f13f | ||
|  | 8211bed449 | ||
|  | b243632483 | ||
|  | e4d2513451 | ||
|  | 385144451b | ||
|  | c8c533844e | ||
|  | 0e69f0c079 | ||
|  | aee60c444f | ||
|  | e7a504c66b | ||
|  | 45d9c7164c | ||
|  | bd913a63a8 | ||
|  | 5a1938c078 | ||
|  | 015cd68756 | ||
|  | 76c0e5b2b8 | ||
|  | 0f8f707acd | ||
|  | 083cccea28 | ||
|  | 31b76b23ce | ||
|  | af529f82e5 | ||
|  | fc6669d254 | ||
|  | c07785be67 | ||
|  | 80d2457b23 | ||
|  | 5dde2752d2 | ||
|  | 8bf4633cd0 | ||
|  | bd66b8a1c8 | ||
|  | be51e533fc | ||
|  | f47ae12019 | ||
|  | cab54a458f | ||
|  | a30734f1bc | ||
|  | 7ad9f7b129 | ||
|  | 40a32e6826 | ||
|  | ab0486aaf1 | ||
|  | 874593a167 | ||
|  | 03bf33630e | ||
|  | 933cce1b94 | ||
|  | 4a6ff573f8 | ||
|  | 1a737f7d19 | ||
|  | cb69914f09 | ||
|  | a372cbb2df | ||
|  | 0ce5caefe8 | ||
|  | 94dabb81f6 | ||
|  | cd45bcfd03 | ||
|  | 49a53f7a45 | ||
|  | 9fa6c0918c | ||
|  | e8d089e37e | ||
|  | a931ce25fa | ||
|  | b507abb4f7 | ||
|  | 66e7c6de62 | ||
|  | 4ce5ea9886 | ||
|  | 8c54b62f07 | ||
|  | 85eb50ed0f | ||
|  | 5ffd621e9d | ||
|  | df93cb09da | ||
|  | bbf04209f0 | ||
|  | 834bfa39c7 | ||
|  | 52b445f70b | ||
|  | 7b9b4fbb0c | ||
|  | 5af0ba1fcb | ||
|  | 85a9748291 | ||
|  | b4005a7ffe | ||
|  | 82de1c88d4 | ||
|  | 1687ed7e0b | ||
|  | c8b9c7d936 | ||
|  | d57057ba28 | ||
|  | 66cee8daa4 | ||
|  | afd7df0942 | ||
|  | bd6ae33d32 | ||
|  | 70660a0d68 | ||
|  | cdad18551a | ||
|  | 592c51d1a5 | ||
|  | 6a57b8a7e7 | ||
|  | 7a94e21c54 | ||
|  | 5b43f321e2 | ||
|  | a4eafb934f | ||
|  | 7b59a665dd | ||
|  | 3d15450ffc | ||
|  | b0c6d52461 | ||
|  | 2dc16dd29f | ||
|  | d8924c536b | ||
|  | 3ebbf2cc46 | ||
|  | f4079604c9 | ||
|  | 1f96a6beab | ||
|  | b277a250e5 | ||
|  | 5b0e1a644d | ||
|  | 6bb3cfa9a3 | ||
|  | 9720868f5a | ||
|  | 8d8ee2a87a | ||
|  | 542e82ee5d | ||
|  | 0104b19502 | ||
|  | 120888b53e | ||
|  | d2e2caed62 | ||
|  | 63066802a8 | ||
|  | 6128bb4ff3 | ||
|  | 982796255d | ||
|  | 36b15f474d | ||
|  | 13f71f8967 | ||
|  | 64336ffbee | ||
|  | b09463d1b2 | ||
|  | b5e6f46b9c | ||
|  | 08af4a0465 | ||
|  | 8c5df6321f | ||
|  | d19f044961 | ||
|  | e378d9f645 | ||
|  | 39dc0f71b4 | ||
|  | 0cef5c6b8c | ||
|  | 9b5a44cef4 | ||
|  | 29769ed91d | ||
|  | 867d794e17 | ||
|  | fdd8458336 | ||
|  | a0bec22e96 | ||
|  | 5aeb5cd214 | ||
|  | e827ddffb9 | ||
|  | 98f80998b9 | 
							
								
								
									
										4
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| node_modules | ||||
| npm-debug.log | ||||
| dist | ||||
| .idea | ||||
| @@ -1,7 +1,9 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <dataSource name="document.db"> | ||||
|   <database-model serializer="dbm" rdbms="SQLITE" format-version="4.7"> | ||||
|     <root id="1"/> | ||||
|   <database-model serializer="dbm" rdbms="SQLITE" format-version="4.9"> | ||||
|     <root id="1"> | ||||
|       <ServerVersion>3.16.1</ServerVersion> | ||||
|     </root> | ||||
|     <schema id="2" parent="1" name="main"> | ||||
|       <Current>1</Current> | ||||
|       <Visible>1</Visible> | ||||
| @@ -48,539 +50,627 @@ | ||||
|       <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> | ||||
|       <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="eventId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>INTEGER|0s</DataType> | ||||
|       <DataType>TEXT|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"> | ||||
|       <ColNames>id</ColNames> | ||||
|     <index id="46" parent="8" name="sqlite_autoindex_event_log_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>eventId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="47" parent="8"> | ||||
|       <ColNames>eventId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_event_log_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <foreign-key id="43" parent="8"> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <RefTableName>notes</RefTableName> | ||||
|       <RefColNames>noteId</RefColNames> | ||||
|     </foreign-key> | ||||
|     <column id="44" parent="9" name="imageId"> | ||||
|     <column id="48" parent="9" name="imageId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="45" parent="9" name="format"> | ||||
|     <column id="49" parent="9" name="format"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="46" parent="9" name="checksum"> | ||||
|     <column id="50" parent="9" name="checksum"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="47" parent="9" name="name"> | ||||
|     <column id="51" parent="9" name="name"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="48" parent="9" name="data"> | ||||
|     <column id="52" parent="9" name="data"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>BLOB|0s</DataType> | ||||
|     </column> | ||||
|     <column id="49" parent="9" name="isDeleted"> | ||||
|     <column id="53" parent="9" name="isDeleted"> | ||||
|       <Position>6</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="50" parent="9" name="dateModified"> | ||||
|     <column id="54" parent="9" name="dateModified"> | ||||
|       <Position>7</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="51" parent="9" name="dateCreated"> | ||||
|     <column id="55" parent="9" name="dateCreated"> | ||||
|       <Position>8</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <index id="52" parent="9" name="sqlite_autoindex_images_1"> | ||||
|     <column id="56" parent="9" name="hash"> | ||||
|       <Position>9</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>""</DefaultExpression> | ||||
|     </column> | ||||
|     <index id="57" parent="9" name="sqlite_autoindex_images_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>imageId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="53" parent="9"> | ||||
|     <key id="58" parent="9"> | ||||
|       <ColNames>imageId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="54" parent="10" name="labelId"> | ||||
|     <column id="59" parent="10" name="labelId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="55" parent="10" name="noteId"> | ||||
|     <column id="60" parent="10" name="noteId"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="56" parent="10" name="name"> | ||||
|     <column id="61" parent="10" name="name"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="57" parent="10" name="value"> | ||||
|     <column id="62" parent="10" name="value"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>''</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="58" parent="10" name="position"> | ||||
|     <column id="63" parent="10" name="position"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="59" parent="10" name="dateCreated"> | ||||
|     <column id="64" parent="10" name="dateCreated"> | ||||
|       <Position>6</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="60" parent="10" name="dateModified"> | ||||
|     <column id="65" parent="10" name="dateModified"> | ||||
|       <Position>7</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="61" parent="10" name="isDeleted"> | ||||
|     <column id="66" parent="10" name="isDeleted"> | ||||
|       <Position>8</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <index id="62" parent="10" name="sqlite_autoindex_labels_1"> | ||||
|     <column id="67" parent="10" name="hash"> | ||||
|       <Position>9</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>""</DefaultExpression> | ||||
|     </column> | ||||
|     <index id="68" parent="10" name="sqlite_autoindex_labels_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>labelId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="63" parent="10" name="IDX_labels_noteId"> | ||||
|     <index id="69" parent="10" name="IDX_labels_noteId"> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="64" parent="10" name="IDX_labels_name_value"> | ||||
|     <index id="70" parent="10" name="IDX_labels_name_value"> | ||||
|       <ColNames>name | ||||
| value</ColNames> | ||||
|       <ColumnCollations> | ||||
| </ColumnCollations> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="65" parent="10"> | ||||
|     <key id="71" parent="10"> | ||||
|       <ColNames>labelId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="66" parent="11" name="noteImageId"> | ||||
|     <column id="72" parent="11" name="noteImageId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="67" parent="11" name="noteId"> | ||||
|     <column id="73" parent="11" name="noteId"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="68" parent="11" name="imageId"> | ||||
|     <column id="74" parent="11" name="imageId"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="69" parent="11" name="isDeleted"> | ||||
|     <column id="75" parent="11" name="isDeleted"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="70" parent="11" name="dateModified"> | ||||
|     <column id="76" parent="11" name="dateModified"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="71" parent="11" name="dateCreated"> | ||||
|     <column id="77" parent="11" name="dateCreated"> | ||||
|       <Position>6</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <index id="72" parent="11" name="sqlite_autoindex_note_images_1"> | ||||
|     <column id="78" parent="11" name="hash"> | ||||
|       <Position>7</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>""</DefaultExpression> | ||||
|     </column> | ||||
|     <index id="79" parent="11" name="sqlite_autoindex_note_images_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>noteImageId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="73" parent="11" name="IDX_note_images_noteId_imageId"> | ||||
|     <index id="80" parent="11" name="IDX_note_images_noteId_imageId"> | ||||
|       <ColNames>noteId | ||||
| imageId</ColNames> | ||||
|       <ColumnCollations> | ||||
| </ColumnCollations> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="74" parent="11" name="IDX_note_images_noteId"> | ||||
|     <index id="81" parent="11" name="IDX_note_images_noteId"> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="75" parent="11" name="IDX_note_images_imageId"> | ||||
|     <index id="82" parent="11" name="IDX_note_images_imageId"> | ||||
|       <ColNames>imageId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="76" parent="11"> | ||||
|     <key id="83" parent="11"> | ||||
|       <ColNames>noteImageId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_note_images_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="77" parent="12" name="noteRevisionId"> | ||||
|     <column id="84" parent="12" name="noteRevisionId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="78" parent="12" name="noteId"> | ||||
|     <column id="85" parent="12" name="noteId"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="79" parent="12" name="title"> | ||||
|     <column id="86" parent="12" name="title"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|     </column> | ||||
|     <column id="80" parent="12" name="content"> | ||||
|     <column id="87" parent="12" name="content"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|     </column> | ||||
|     <column id="81" parent="12" name="isProtected"> | ||||
|     <column id="88" parent="12" name="isProtected"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="82" parent="12" name="dateModifiedFrom"> | ||||
|     <column id="89" parent="12" name="dateModifiedFrom"> | ||||
|       <Position>6</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="83" parent="12" name="dateModifiedTo"> | ||||
|     <column id="90" parent="12" name="dateModifiedTo"> | ||||
|       <Position>7</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <index id="84" parent="12" name="sqlite_autoindex_note_revisions_1"> | ||||
|     <column id="91" parent="12" name="type"> | ||||
|       <Position>8</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>''</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="92" parent="12" name="mime"> | ||||
|       <Position>9</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>''</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="93" parent="12" name="hash"> | ||||
|       <Position>10</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>""</DefaultExpression> | ||||
|     </column> | ||||
|     <index id="94" parent="12" name="sqlite_autoindex_note_revisions_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>noteRevisionId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="85" parent="12" name="IDX_note_revisions_noteId"> | ||||
|     <index id="95" parent="12" name="IDX_note_revisions_noteId"> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="86" parent="12" name="IDX_note_revisions_dateModifiedFrom"> | ||||
|     <index id="96" parent="12" name="IDX_note_revisions_dateModifiedFrom"> | ||||
|       <ColNames>dateModifiedFrom</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="87" parent="12" name="IDX_note_revisions_dateModifiedTo"> | ||||
|     <index id="97" parent="12" name="IDX_note_revisions_dateModifiedTo"> | ||||
|       <ColNames>dateModifiedTo</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="88" parent="12"> | ||||
|     <key id="98" parent="12"> | ||||
|       <ColNames>noteRevisionId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="89" parent="13" name="noteId"> | ||||
|     <column id="99" parent="13" name="noteId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="90" parent="13" name="title"> | ||||
|     <column id="100" parent="13" name="title"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>"unnamed"</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="91" parent="13" name="content"> | ||||
|     <column id="101" parent="13" name="content"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>""</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="92" parent="13" name="isProtected"> | ||||
|     <column id="102" parent="13" name="isProtected"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="93" parent="13" name="isDeleted"> | ||||
|     <column id="103" parent="13" name="isDeleted"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="94" parent="13" name="dateCreated"> | ||||
|     <column id="104" parent="13" name="dateCreated"> | ||||
|       <Position>6</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="95" parent="13" name="dateModified"> | ||||
|     <column id="105" parent="13" name="dateModified"> | ||||
|       <Position>7</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="96" parent="13" name="type"> | ||||
|     <column id="106" parent="13" name="type"> | ||||
|       <Position>8</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>'text'</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="97" parent="13" name="mime"> | ||||
|     <column id="107" parent="13" name="mime"> | ||||
|       <Position>9</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>'text/html'</DefaultExpression> | ||||
|     </column> | ||||
|     <index id="98" parent="13" name="sqlite_autoindex_notes_1"> | ||||
|     <column id="108" parent="13" name="hash"> | ||||
|       <Position>10</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>""</DefaultExpression> | ||||
|     </column> | ||||
|     <index id="109" parent="13" name="sqlite_autoindex_notes_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="99" parent="13" name="IDX_notes_isDeleted"> | ||||
|       <ColNames>isDeleted</ColNames> | ||||
|     <index id="110" parent="13" name="IDX_notes_type"> | ||||
|       <ColNames>type</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="100" parent="13"> | ||||
|     <key id="111" parent="13"> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="101" parent="14" name="name"> | ||||
|     <column id="112" parent="14" name="optionId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="102" parent="14" name="value"> | ||||
|     <column id="113" parent="14" name="name"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="103" parent="14" name="dateModified"> | ||||
|     <column id="114" parent="14" name="value"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|     </column> | ||||
|     <column id="115" parent="14" name="dateModified"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|     </column> | ||||
|     <column id="104" parent="14" name="isSynced"> | ||||
|       <Position>4</Position> | ||||
|     <column id="116" parent="14" name="isSynced"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>INTEGER|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <index id="105" parent="14" name="sqlite_autoindex_options_1"> | ||||
|     <column id="117" parent="14" name="hash"> | ||||
|       <Position>6</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>""</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="118" parent="14" name="dateCreated"> | ||||
|       <Position>7</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>'1970-01-01T00:00:00.000Z'</DefaultExpression> | ||||
|     </column> | ||||
|     <index id="119" parent="14" name="sqlite_autoindex_options_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>name</ColNames> | ||||
|       <ColNames>optionId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="106" parent="14"> | ||||
|       <ColNames>name</ColNames> | ||||
|     <key id="120" parent="14"> | ||||
|       <ColNames>optionId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="107" parent="15" name="branchId"> | ||||
|     <column id="121" parent="15" name="branchId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="108" parent="15" name="notePath"> | ||||
|     <column id="122" parent="15" name="notePath"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="109" parent="15" name="dateAccessed"> | ||||
|     <column id="123" parent="15" name="hash"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>""</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="110" parent="15" name="isDeleted"> | ||||
|     <column id="124" parent="15" name="dateCreated"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="125" parent="15" name="isDeleted"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>INT|0s</DataType> | ||||
|     </column> | ||||
|     <index id="111" parent="15" name="sqlite_autoindex_recent_notes_1"> | ||||
|     <index id="126" parent="15" name="sqlite_autoindex_recent_notes_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>branchId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="112" parent="15"> | ||||
|     <key id="127" parent="15"> | ||||
|       <ColNames>branchId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="113" parent="16" name="sourceId"> | ||||
|     <column id="128" parent="16" name="sourceId"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="114" parent="16" name="dateCreated"> | ||||
|     <column id="129" parent="16" name="dateCreated"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <index id="115" parent="16" name="sqlite_autoindex_source_ids_1"> | ||||
|     <index id="130" parent="16" name="sqlite_autoindex_source_ids_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>sourceId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="116" parent="16"> | ||||
|     <key id="131" parent="16"> | ||||
|       <ColNames>sourceId</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|       <UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName> | ||||
|     </key> | ||||
|     <column id="117" parent="17" name="type"> | ||||
|     <column id="132" parent="17" name="type"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>text|0s</DataType> | ||||
|     </column> | ||||
|     <column id="118" parent="17" name="name"> | ||||
|     <column id="133" parent="17" name="name"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>text|0s</DataType> | ||||
|     </column> | ||||
|     <column id="119" parent="17" name="tbl_name"> | ||||
|     <column id="134" parent="17" name="tbl_name"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>text|0s</DataType> | ||||
|     </column> | ||||
|     <column id="120" parent="17" name="rootpage"> | ||||
|     <column id="135" parent="17" name="rootpage"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>integer|0s</DataType> | ||||
|     </column> | ||||
|     <column id="121" parent="17" name="sql"> | ||||
|     <column id="136" parent="17" name="sql"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>text|0s</DataType> | ||||
|     </column> | ||||
|     <column id="122" parent="18" name="name"> | ||||
|     <column id="137" parent="18" name="name"> | ||||
|       <Position>1</Position> | ||||
|     </column> | ||||
|     <column id="123" parent="18" name="seq"> | ||||
|     <column id="138" parent="18" name="seq"> | ||||
|       <Position>2</Position> | ||||
|     </column> | ||||
|     <column id="124" parent="19" name="id"> | ||||
|     <column id="139" parent="19" name="id"> | ||||
|       <Position>1</Position> | ||||
|       <DataType>INTEGER|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <SequenceIdentity>1</SequenceIdentity> | ||||
|     </column> | ||||
|     <column id="125" parent="19" name="entityName"> | ||||
|     <column id="140" parent="19" name="entityName"> | ||||
|       <Position>2</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="126" parent="19" name="entityId"> | ||||
|     <column id="141" parent="19" name="entityId"> | ||||
|       <Position>3</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="127" parent="19" name="sourceId"> | ||||
|     <column id="142" parent="19" name="sourceId"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="128" parent="19" name="syncDate"> | ||||
|     <column id="143" parent="19" name="syncDate"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <index id="129" parent="19" name="IDX_sync_entityName_entityId"> | ||||
|     <index id="144" parent="19" name="IDX_sync_entityName_entityId"> | ||||
|       <ColNames>entityName | ||||
| entityId</ColNames> | ||||
|       <ColumnCollations> | ||||
| </ColumnCollations> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="130" parent="19" name="IDX_sync_syncDate"> | ||||
|     <index id="145" parent="19" name="IDX_sync_syncDate"> | ||||
|       <ColNames>syncDate</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="131" parent="19"> | ||||
|     <key id="146" parent="19"> | ||||
|       <ColNames>id</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|     </key> | ||||
|   | ||||
							
								
								
									
										21
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | ||||
| FROM node:8.11.2 | ||||
|  | ||||
| RUN apt-get update && apt-get install -y nasm | ||||
|  | ||||
| # Create app directory | ||||
| WORKDIR /usr/src/app | ||||
|  | ||||
| # Install app dependencies | ||||
| # A wildcard is used to ensure both package.json AND package-lock.json are copied | ||||
| # where available (npm@5+) | ||||
| COPY package*.json ./ | ||||
|  | ||||
| RUN npm install --production | ||||
| # If you are building your code for production | ||||
| # RUN npm install --only=production | ||||
|  | ||||
| # Bundle app source | ||||
| COPY . . | ||||
|  | ||||
| EXPOSE 8080 | ||||
| CMD [ "node", "src/www" ] | ||||
							
								
								
									
										8
									
								
								bin/build-docker.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| if [[ $# -eq 0 ]] ; then | ||||
|     echo "Missing argument of new version" | ||||
|     exit 1 | ||||
| fi | ||||
|  | ||||
| docker build -t zadam/trilium:latest -t zadam/trilium:$1 . | ||||
							
								
								
									
										9
									
								
								bin/push-docker-image.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| if [[ $# -eq 0 ]] ; then | ||||
|     echo "Missing argument of new version" | ||||
|     exit 1 | ||||
| fi | ||||
|  | ||||
| docker push zadam/trilium:latest | ||||
| docker push zadam/trilium:$1 | ||||
| @@ -24,9 +24,9 @@ jq '.version = "'$VERSION'"' package.json|sponge package.json | ||||
|  | ||||
| git add package.json | ||||
|  | ||||
| echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > services/build.js | ||||
| echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > src/services/build.js | ||||
|  | ||||
| git add services/build.js | ||||
| git add src/services/build.js | ||||
|  | ||||
| TAG=v$VERSION | ||||
|  | ||||
| @@ -75,4 +75,8 @@ github-release upload \ | ||||
|     --name "$WINDOWS_X64_BUILD" \ | ||||
|     --file "dist/$WINDOWS_X64_BUILD" | ||||
|  | ||||
| bin/build-docker.sh $VERSION | ||||
|  | ||||
| bin/push-docker-image.sh $VERSION | ||||
|  | ||||
| echo "Release finished!" | ||||
| @@ -1,3 +1,4 @@ | ||||
| INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('root', 'root', 'none', 0, null, 1, 0, '2018-01-01T00:00:00.000Z'); | ||||
| INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('dLgtLUFn3GoN', '1Heh2acXfPNt', 'root', 21, null, 1, 0, '2017-12-23T00:46:39.304Z'); | ||||
| INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('QLfS835GSfIh', '3RkyK9LI18dO', '1Heh2acXfPNt', 1, null, 1, 0, '2017-12-23T01:20:04.181Z'); | ||||
| INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('QJAcYJ1gGUh9', 'L1Ox40M1aEyy', '3RkyK9LI18dO', 0, null, 0, 0, '2017-12-23T01:20:45.365Z'); | ||||
|   | ||||
							
								
								
									
										5
									
								
								db/migrations/0087__add_type_mime_to_note_revision.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | ||||
| ALTER TABLE note_revisions ADD type TEXT DEFAULT '' NOT NULL; | ||||
| ALTER TABLE note_revisions ADD mime TEXT DEFAULT '' NOT NULL; | ||||
|  | ||||
| UPDATE note_revisions SET type = (SELECT type FROM notes WHERE notes.noteId = note_revisions.noteId); | ||||
| UPDATE note_revisions SET mime = (SELECT mime FROM notes WHERE notes.noteId = note_revisions.noteId); | ||||
							
								
								
									
										34
									
								
								db/migrations/0088__non_null_note_title_content.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,34 @@ | ||||
| CREATE TABLE event_logc027 | ||||
| ( | ||||
|   id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, | ||||
|   noteId TEXT, | ||||
|   comment TEXT, | ||||
|   dateAdded TEXT NOT NULL | ||||
| ); | ||||
| INSERT INTO event_logc027(id, noteId, comment, dateAdded) SELECT id, noteId, comment, dateAdded FROM event_log; | ||||
| DROP TABLE event_log; | ||||
| ALTER TABLE event_logc027 RENAME TO event_log; | ||||
|  | ||||
| CREATE TABLE IF NOT EXISTS "notes_mig" ( | ||||
|   `noteId`	TEXT NOT NULL, | ||||
|   `title`	TEXT NOT NULL DEFAULT "unnamed", | ||||
|   `content`	TEXT NOT NULL DEFAULT "", | ||||
|   `isProtected`	INT NOT NULL DEFAULT 0, | ||||
|   `isDeleted`	INT NOT NULL DEFAULT 0, | ||||
|   `dateCreated`	TEXT NOT NULL, | ||||
|   `dateModified`	TEXT NOT NULL, | ||||
|   type TEXT NOT NULL DEFAULT 'text', | ||||
|   mime TEXT NOT NULL DEFAULT 'text/html', | ||||
|   PRIMARY KEY(`noteId`) | ||||
| ); | ||||
|  | ||||
| INSERT INTO notes_mig (noteId, title, content, isProtected, isDeleted, dateCreated, dateModified, type, mime) | ||||
|     SELECT noteId, title, content, isProtected, isDeleted, dateCreated, dateModified, type, mime FROM notes; | ||||
|  | ||||
| DROP TABLE notes; | ||||
|  | ||||
| ALTER TABLE notes_mig RENAME TO notes; | ||||
|  | ||||
| CREATE INDEX `IDX_notes_isDeleted` ON `notes` ( | ||||
|   `isDeleted` | ||||
| ); | ||||
							
								
								
									
										5
									
								
								db/migrations/0089__add_root_branch.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | ||||
| INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, dateModified) | ||||
|     VALUES ('root', 'root', 'none', 0, null, 1, '2018-01-01T00:00:00.000Z'); | ||||
|  | ||||
| INSERT INTO sync (entityName, entityId, sourceId, syncDate) | ||||
|     VALUES ('branches' ,'root', 'SYNC_FILL', '2018-01-01T00:00:00.000Z'); | ||||
							
								
								
									
										1
									
								
								db/migrations/0090__branch_index.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | ||||
| CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId); | ||||
							
								
								
									
										2
									
								
								db/migrations/0091__drop_isDeleted_index.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | ||||
| -- index confuses planner and is mostly useless anyway since we're mostly used in non-deleted notes (which are presumably majority) | ||||
| DROP INDEX IDX_notes_isDeleted; | ||||
							
								
								
									
										2
									
								
								db/migrations/0092__add_type_index.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | ||||
| create index IDX_notes_type | ||||
|   on notes (type); | ||||
							
								
								
									
										9
									
								
								db/migrations/0093__add_hash_field.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | ||||
| ALTER TABLE notes ADD hash TEXT DEFAULT "" NOT NULL; | ||||
| ALTER TABLE branches ADD hash TEXT DEFAULT "" NOT NULL; | ||||
| ALTER TABLE note_revisions ADD hash TEXT DEFAULT "" NOT NULL; | ||||
| ALTER TABLE recent_notes ADD hash TEXT DEFAULT "" NOT NULL; | ||||
| ALTER TABLE options ADD hash TEXT DEFAULT "" NOT NULL; | ||||
| ALTER TABLE note_images ADD hash TEXT DEFAULT "" NOT NULL; | ||||
| ALTER TABLE images ADD hash TEXT DEFAULT "" NOT NULL; | ||||
| ALTER TABLE labels ADD hash TEXT DEFAULT "" NOT NULL; | ||||
| ALTER TABLE api_tokens ADD hash TEXT DEFAULT "" NOT NULL; | ||||
							
								
								
									
										30
									
								
								db/migrations/0094__unify_auditing_fields.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,30 @@ | ||||
| ALTER TABLE branches ADD dateCreated TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z'; | ||||
|  | ||||
| CREATE TABLE `event_log_mig` ( | ||||
|   `id`	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||
|   `noteId`	TEXT, | ||||
|   `comment`	TEXT, | ||||
|   `dateCreated`	TEXT NOT NULL | ||||
| ); | ||||
|  | ||||
| INSERT INTO event_log_mig (id, noteId, comment, dateCreated) | ||||
| SELECT id, noteId, comment, dateAdded FROM event_log; | ||||
|  | ||||
| DROP TABLE event_log; | ||||
| ALTER TABLE event_log_mig RENAME TO event_log; | ||||
|  | ||||
| ALTER TABLE options ADD dateCreated TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z'; | ||||
|  | ||||
| CREATE TABLE `recent_notes_mig` ( | ||||
|   `branchId` TEXT NOT NULL PRIMARY KEY, | ||||
|   `notePath` TEXT NOT NULL, | ||||
|   hash TEXT DEFAULT "" NOT NULL, | ||||
|   `dateCreated` TEXT NOT NULL, | ||||
|   isDeleted INT | ||||
| ); | ||||
|  | ||||
| INSERT INTO recent_notes_mig (branchId, notePath, hash, dateCreated, isDeleted) | ||||
| SELECT branchId, notePath, hash, dateAccessed, isDeleted FROM recent_notes; | ||||
|  | ||||
| DROP TABLE recent_notes; | ||||
| ALTER TABLE recent_notes_mig RENAME TO recent_notes; | ||||
							
								
								
									
										1
									
								
								db/migrations/0095__mime_type_for_render.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | ||||
| UPDATE notes SET mime = 'text/html' WHERE type = 'render'; | ||||
							
								
								
									
										29
									
								
								db/migrations/0096__unify_surrogate_keys.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,29 @@ | ||||
| CREATE TABLE `event_log_mig` ( | ||||
|   `eventId`	TEXT NOT NULL PRIMARY KEY, | ||||
|   `noteId`	TEXT, | ||||
|   `comment`	TEXT, | ||||
|   `dateCreated`	TEXT NOT NULL | ||||
| ); | ||||
|  | ||||
| INSERT INTO event_log_mig (eventId, noteId, comment, dateCreated) | ||||
| SELECT id, noteId, comment, dateCreated FROM event_log; | ||||
|  | ||||
| DROP TABLE event_log; | ||||
| ALTER TABLE event_log_mig RENAME TO event_log; | ||||
|  | ||||
| create table options_mig | ||||
| ( | ||||
|   optionId TEXT NOT NULL PRIMARY KEY, | ||||
|   name TEXT not null, | ||||
|   value TEXT, | ||||
|   dateModified INT, | ||||
|   isSynced INTEGER default 0 not null, | ||||
|   hash TEXT default "" not null, | ||||
|   dateCreated TEXT default '1970-01-01T00:00:00.000Z' not null | ||||
| ); | ||||
|  | ||||
| INSERT INTO options_mig (optionId, name, value, dateModified, isSynced, hash, dateCreated) | ||||
|   SELECT name || "_key", name, value, dateModified, isSynced, hash, dateCreated FROM options; | ||||
|  | ||||
| DROP TABLE options; | ||||
| ALTER TABLE options_mig RENAME TO options; | ||||
							
								
								
									
										2
									
								
								db/migrations/0097__add_zoomFactor.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | ||||
| INSERT INTO options (optionId, name, value, dateCreated, dateModified, isSynced) | ||||
| VALUES ('zoomFactor_key', 'zoomFactor', '1.0', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0); | ||||
							
								
								
									
										1
									
								
								db/migrations/0098__rename_hideInAutocomplete.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | ||||
| UPDATE labels SET name = 'archived' WHERE name = 'hideInAutocomplete' | ||||
							
								
								
									
										2
									
								
								db/migrations/0099__add_theme_option.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | ||||
| INSERT INTO options (optionId, name, value, dateCreated, dateModified, isSynced) | ||||
| VALUES ('theme_key', 'theme', 'white', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0); | ||||
							
								
								
									
										15
									
								
								db/migrations/0100__remove_optionId.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,15 @@ | ||||
| create table options_mig | ||||
| ( | ||||
|   name TEXT not null PRIMARY KEY, | ||||
|   value TEXT, | ||||
|   dateModified INT, | ||||
|   isSynced INTEGER default 0 not null, | ||||
|   hash TEXT default "" not null, | ||||
|   dateCreated TEXT default '1970-01-01T00:00:00.000Z' not null | ||||
| ); | ||||
|  | ||||
| INSERT INTO options_mig (name, value, dateModified, isSynced, hash, dateCreated) | ||||
| SELECT name, value, dateModified, isSynced, hash, dateCreated FROM options; | ||||
|  | ||||
| DROP TABLE options; | ||||
| ALTER TABLE options_mig RENAME TO options; | ||||
| @@ -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, | ||||
| @@ -21,28 +16,6 @@ CREATE TABLE IF NOT EXISTS "source_ids" ( | ||||
|   `dateCreated`	TEXT NOT NULL, | ||||
|   PRIMARY KEY(`sourceId`) | ||||
| ); | ||||
| CREATE TABLE IF NOT EXISTS "notes" ( | ||||
|   `noteId`	TEXT NOT NULL, | ||||
|   `title`	TEXT, | ||||
|   `content`	TEXT, | ||||
|   `isProtected`	INT NOT NULL DEFAULT 0, | ||||
|   `isDeleted`	INT NOT NULL DEFAULT 0, | ||||
|   `dateCreated`	TEXT NOT NULL, | ||||
|   `dateModified`	TEXT NOT NULL, | ||||
|   type TEXT NOT NULL DEFAULT 'text', | ||||
|   mime TEXT NOT NULL DEFAULT 'text/html', | ||||
|   PRIMARY KEY(`noteId`) | ||||
| ); | ||||
| CREATE INDEX `IDX_notes_isDeleted` ON `notes` ( | ||||
|   `isDeleted` | ||||
| ); | ||||
| CREATE TABLE IF NOT EXISTS "event_log" ( | ||||
|   `id`	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||
|   `noteId`	TEXT, | ||||
|   `comment`	TEXT, | ||||
|   `dateAdded`	TEXT NOT NULL, | ||||
|   FOREIGN KEY(noteId) REFERENCES notes(noteId) | ||||
| ); | ||||
| CREATE TABLE IF NOT EXISTS "note_revisions" ( | ||||
|   `noteRevisionId`	TEXT NOT NULL PRIMARY KEY, | ||||
|   `noteId`	TEXT NOT NULL, | ||||
| @@ -51,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, hash TEXT DEFAULT "" NOT NULL); | ||||
| CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` ( | ||||
|   `noteId` | ||||
| ); | ||||
| @@ -71,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, | ||||
| @@ -80,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); | ||||
| @@ -90,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, | ||||
| @@ -99,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` ( | ||||
| @@ -109,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, | ||||
| @@ -125,8 +92,45 @@ 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 "notes" ( | ||||
|   `noteId`	TEXT NOT NULL, | ||||
|   `title`	TEXT NOT NULL DEFAULT "unnamed", | ||||
|   `content`	TEXT NOT NULL DEFAULT "", | ||||
|   `isProtected`	INT NOT NULL DEFAULT 0, | ||||
|   `isDeleted`	INT NOT NULL DEFAULT 0, | ||||
|   `dateCreated`	TEXT NOT NULL, | ||||
|   `dateModified`	TEXT NOT NULL, | ||||
|   type TEXT NOT NULL DEFAULT 'text', | ||||
|   mime TEXT NOT NULL DEFAULT 'text/html', hash TEXT DEFAULT "" NOT NULL, | ||||
|   PRIMARY KEY(`noteId`) | ||||
| ); | ||||
| CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId); | ||||
| CREATE INDEX IDX_notes_type | ||||
|   on notes (type); | ||||
| CREATE TABLE IF NOT EXISTS "recent_notes" ( | ||||
|   `branchId` TEXT NOT NULL PRIMARY KEY, | ||||
|   `notePath` TEXT NOT NULL, | ||||
|   hash TEXT DEFAULT "" NOT NULL, | ||||
|   `dateCreated` TEXT NOT NULL, | ||||
|   isDeleted INT | ||||
| ); | ||||
| CREATE TABLE IF NOT EXISTS "event_log" ( | ||||
|   `eventId`	TEXT NOT NULL PRIMARY KEY, | ||||
|   `noteId`	TEXT, | ||||
|   `comment`	TEXT, | ||||
|   `dateCreated`	TEXT NOT NULL | ||||
| ); | ||||
| CREATE TABLE IF NOT EXISTS "options" | ||||
| ( | ||||
|   name TEXT not null PRIMARY KEY, | ||||
|   value TEXT, | ||||
|   dateModified INT, | ||||
|   isSynced INTEGER default 0 not null, | ||||
|   hash TEXT default "" not null, | ||||
|   dateCreated TEXT default '1970-01-01T00:00:00.000Z' not null | ||||
| ); | ||||
|   | ||||
| @@ -76,12 +76,12 @@ app.on('ready', () => { | ||||
|         const dateNoteService = require('./src/services/date_notes'); | ||||
|         const dateUtils = require('./src/services/date_utils'); | ||||
|  | ||||
|         const parentNoteId = await dateNoteService.getDateNoteId(dateUtils.nowDate()); | ||||
|         const parentNote = await dateNoteService.getDateNote(dateUtils.nowDate()); | ||||
|  | ||||
|         // window may be hidden / not in focus | ||||
|         mainWindow.focus(); | ||||
|  | ||||
|         mainWindow.webContents.send('create-day-sub-note', parentNoteId); | ||||
|         mainWindow.webContents.send('create-day-sub-note', parentNote.noteId); | ||||
|     }); | ||||
|  | ||||
|     if (!result) { | ||||
|   | ||||
							
								
								
									
										8509
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										44
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "name": "trilium", | ||||
|   "description": "Trilium Notes", | ||||
|   "version": "0.10.0-beta", | ||||
|   "version": "0.16.0", | ||||
|   "license": "AGPL-3.0-only", | ||||
|   "main": "electron.js", | ||||
|   "repository": { | ||||
| @@ -9,11 +9,11 @@ | ||||
|     "url": "https://github.com/zadam/trilium.git" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "start": "node ./bin/www", | ||||
|     "start": "node ./src/www", | ||||
|     "test-electron": "xo", | ||||
|     "rebuild-electron": "electron-rebuild", | ||||
|     "start-electron": "electron . --disable-gpu", | ||||
|     "build-electron": "electron-packager . --out=dist --asar --overwrite --platform=win32,linux --arch=ia32,x64", | ||||
|     "build-electron": "electron-packager . --out=dist --asar --overwrite --platform=win32,linux --arch=ia32,x64 --app-version=", | ||||
|     "start-forge": "electron-forge start", | ||||
|     "package-forge": "electron-forge package", | ||||
|     "make-forge": "electron-forge make", | ||||
| @@ -21,22 +21,20 @@ | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "async-mutex": "^0.1.3", | ||||
|     "axios": "^0.17.1", | ||||
|     "body-parser": "~1.18.2", | ||||
|     "axios": "^0.18", | ||||
|     "body-parser": "^1.18.3", | ||||
|     "cls-hooked": "^4.2.2", | ||||
|     "cookie-parser": "~1.4.3", | ||||
|     "debug": "~3.1.0", | ||||
|     "devtron": "^1.4.0", | ||||
|     "ejs": "~2.5.7", | ||||
|     "electron": "^2.0.0-beta.5", | ||||
|     "ejs": "~2.6.1", | ||||
|     "electron-debug": "^1.5.0", | ||||
|     "electron-dl": "^1.11.0", | ||||
|     "electron-in-page-search": "^1.2.4", | ||||
|     "electron-rebuild": "^1.7.3", | ||||
|     "electron-dl": "^1.12.0", | ||||
|     "electron-in-page-search": "^1.3.2", | ||||
|     "express": "~4.16.3", | ||||
|     "express-session": "^1.15.6", | ||||
|     "fs-extra": "^4.0.3", | ||||
|     "helmet": "^3.12.0", | ||||
|     "fs-extra": "^6.0.1", | ||||
|     "helmet": "^3.12.1", | ||||
|     "html": "^1.0.0", | ||||
|     "image-type": "^3.0.0", | ||||
|     "imagemin": "^5.3.1", | ||||
| @@ -45,30 +43,34 @@ | ||||
|     "imagemin-pngquant": "^5.1.0", | ||||
|     "ini": "^1.3.5", | ||||
|     "jimp": "^0.2.28", | ||||
|     "moment": "^2.21.0", | ||||
|     "moment": "^2.22.1", | ||||
|     "multer": "^1.3.0", | ||||
|     "open": "0.0.5", | ||||
|     "rand-token": "^0.4.0", | ||||
|     "request": "^2.85.0", | ||||
|     "rcedit": "^1.1.0", | ||||
|     "request": "^2.87.0", | ||||
|     "request-promise": "^4.2.2", | ||||
|     "rimraf": "^2.6.2", | ||||
|     "sanitize-filename": "^1.6.1", | ||||
|     "scrypt": "^6.0.3", | ||||
|     "serve-favicon": "~2.4.5", | ||||
|     "serve-favicon": "~2.5.0", | ||||
|     "session-file-store": "^1.2.0", | ||||
|     "simple-node-logger": "^0.93.37", | ||||
|     "sqlite": "^2.9.1", | ||||
|     "tar-stream": "^1.5.5", | ||||
|     "sqlite": "^2.9.2", | ||||
|     "tar-stream": "^1.6.1", | ||||
|     "unescape": "^1.0.1", | ||||
|     "ws": "^3.3.3" | ||||
|     "ws": "^5.2.0", | ||||
|     "xml2js": "^0.4.19" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "electron": "^2.0.1", | ||||
|     "electron-compile": "^6.4.2", | ||||
|     "electron-packager": "^11.1.0", | ||||
|     "electron-prebuilt-compile": "2.0.0-beta.5", | ||||
|     "electron-packager": "^12.1.0", | ||||
|     "electron-prebuilt-compile": "2.0.0", | ||||
|     "electron-rebuild": "^1.7.3", | ||||
|     "lorem-ipsum": "^1.0.4", | ||||
|     "tape": "^4.9.0", | ||||
|     "xo": "^0.18.0" | ||||
|     "xo": "^0.21.1" | ||||
|   }, | ||||
|   "config": { | ||||
|     "forge": { | ||||
|   | ||||
| @@ -6,6 +6,7 @@ const dateUtils = require('../services/date_utils'); | ||||
| class ApiToken extends Entity { | ||||
|     static get tableName() { return "api_tokens"; } | ||||
|     static get primaryKeyName() { return "apiTokenId"; } | ||||
|     static get hashedProperties() { return ["apiTokenId", "token", "dateCreated", "isDeleted"]; } | ||||
|  | ||||
|     beforeSaving() { | ||||
|         super.beforeSaving(); | ||||
|   | ||||
| @@ -8,6 +8,8 @@ const sql = require('../services/sql'); | ||||
| class Branch extends Entity { | ||||
|     static get tableName() { return "branches"; } | ||||
|     static get primaryKeyName() { return "branchId"; } | ||||
|     // notePosition is not part of hash because it would produce a lot of updates in case of reordering | ||||
|     static get hashedProperties() { return ["branchId", "noteId", "parentNoteId", "dateModified", "isDeleted", "prefix"]; } | ||||
|  | ||||
|     async getNote() { | ||||
|         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]); | ||||
| @@ -25,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(); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -14,6 +14,14 @@ class Entity { | ||||
|         if (!this[this.constructor.primaryKeyName]) { | ||||
|             this[this.constructor.primaryKeyName] = utils.newEntityId(); | ||||
|         } | ||||
|  | ||||
|         let contentToHash = ""; | ||||
|  | ||||
|         for (const propertyName of this.constructor.hashedProperties) { | ||||
|             contentToHash += "|" + this[propertyName]; | ||||
|         } | ||||
|  | ||||
|         this["hash"] = utils.hash(contentToHash).substr(0, 10); | ||||
|     } | ||||
|  | ||||
|     async save() { | ||||
|   | ||||
| @@ -6,6 +6,7 @@ const Branch = require('../entities/branch'); | ||||
| const Label = require('../entities/label'); | ||||
| const RecentNote = require('../entities/recent_note'); | ||||
| const ApiToken = require('../entities/api_token'); | ||||
| const Option = require('../entities/option'); | ||||
| const repository = require('../services/repository'); | ||||
|  | ||||
| function createEntityFromRow(row) { | ||||
| @@ -35,6 +36,9 @@ function createEntityFromRow(row) { | ||||
|     else if (row.noteId) { | ||||
|         entity = new Note(row); | ||||
|     } | ||||
|     else if (row.name) { | ||||
|         entity = new Option(row); | ||||
|     } | ||||
|     else { | ||||
|         throw new Error('Unknown entity type for row: ' + JSON.stringify(row)); | ||||
|     } | ||||
|   | ||||
| @@ -6,6 +6,7 @@ const dateUtils = require('../services/date_utils'); | ||||
| class Image extends Entity { | ||||
|     static get tableName() { return "images"; } | ||||
|     static get primaryKeyName() { return "imageId"; } | ||||
|     static get hashedProperties() { return ["imageId", "format", "checksum", "name", "isDeleted", "dateModified", "dateCreated"]; } | ||||
|  | ||||
|     beforeSaving() { | ||||
|         super.beforeSaving(); | ||||
|   | ||||
| @@ -8,6 +8,7 @@ const sql = require('../services/sql'); | ||||
| class Label extends Entity { | ||||
|     static get tableName() { return "labels"; } | ||||
|     static get primaryKeyName() { return "labelId"; } | ||||
|     static get hashedProperties() { return ["labelId", "noteId", "name", "value", "dateModified", "dateCreated"]; } | ||||
|  | ||||
|     async getNote() { | ||||
|         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]); | ||||
|   | ||||
| @@ -1,24 +1,33 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const Entity = require('./entity'); | ||||
| const protected_session = require('../services/protected_session'); | ||||
| const protectedSessionService = require('../services/protected_session'); | ||||
| const repository = require('../services/repository'); | ||||
| const dateUtils = require('../services/date_utils'); | ||||
|  | ||||
| class Note extends Entity { | ||||
|     static get tableName() { return "notes"; } | ||||
|     static get primaryKeyName() { return "noteId"; } | ||||
|     static get hashedProperties() { return ["noteId", "title", "content", "type", "dateModified", "isProtected", "isDeleted"]; } | ||||
|  | ||||
|     constructor(row) { | ||||
|         super(row); | ||||
|  | ||||
|         if (this.isProtected) { | ||||
|             protected_session.decryptNote(this); | ||||
|         // check if there's noteId, otherwise this is a new entity which wasn't encrypted yet | ||||
|         if (this.isProtected && this.noteId) { | ||||
|             protectedSessionService.decryptNote(this); | ||||
|         } | ||||
|  | ||||
|         if (this.isJson()) { | ||||
|         this.setContent(this.content); | ||||
|     } | ||||
|  | ||||
|     setContent(content) { | ||||
|         this.content = content; | ||||
|  | ||||
|         try { | ||||
|             this.jsonContent = JSON.parse(this.content); | ||||
|         } | ||||
|         catch(e) {} | ||||
|     } | ||||
|  | ||||
|     isJson() { | ||||
| @@ -31,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() { | ||||
| @@ -133,12 +142,12 @@ class Note extends Entity { | ||||
|     beforeSaving() { | ||||
|         super.beforeSaving(); | ||||
|  | ||||
|         if (this.isJson()) { | ||||
|         if (this.isJson() && this.jsonContent) { | ||||
|             this.content = JSON.stringify(this.jsonContent, null, '\t'); | ||||
|         } | ||||
|  | ||||
|         if (this.isProtected) { | ||||
|             protected_session.encryptNote(this); | ||||
|             protectedSessionService.encryptNote(this); | ||||
|         } | ||||
|  | ||||
|         if (!this.isDeleted) { | ||||
|   | ||||
| @@ -7,6 +7,7 @@ const dateUtils = require('../services/date_utils'); | ||||
| class NoteImage extends Entity { | ||||
|     static get tableName() { return "note_images"; } | ||||
|     static get primaryKeyName() { return "noteImageId"; } | ||||
|     static get hashedProperties() { return ["noteImageId", "noteId", "imageId", "isDeleted", "dateModified", "dateCreated"]; } | ||||
|  | ||||
|     async getNote() { | ||||
|         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]); | ||||
|   | ||||
| @@ -1,19 +1,19 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const Entity = require('./entity'); | ||||
| const protected_session = require('../services/protected_session'); | ||||
| const utils = require('../services/utils'); | ||||
| const protectedSessionService = require('../services/protected_session'); | ||||
| const repository = require('../services/repository'); | ||||
|  | ||||
| class NoteRevision extends Entity { | ||||
|     static get tableName() { return "note_revisions"; } | ||||
|     static get primaryKeyName() { return "noteRevisionId"; } | ||||
|     static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "content", "dateModifiedFrom", "dateModifiedTo"]; } | ||||
|  | ||||
|     constructor(row) { | ||||
|         super(row); | ||||
|  | ||||
|         if (this.isProtected) { | ||||
|             protected_session.decryptNoteRevision(this); | ||||
|             protectedSessionService.decryptNoteRevision(this); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -25,7 +25,7 @@ class NoteRevision extends Entity { | ||||
|         super.beforeSaving(); | ||||
|  | ||||
|         if (this.isProtected) { | ||||
|             protected_session.encryptNoteRevision(this); | ||||
|             protectedSessionService.encryptNoteRevision(this); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										18
									
								
								src/entities/option.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const Entity = require('./entity'); | ||||
| 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"]; } | ||||
|  | ||||
|     beforeSaving() { | ||||
|         super.beforeSaving(); | ||||
|  | ||||
|         this.dateModified = dateUtils.nowDate(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = Option; | ||||
| @@ -1,10 +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", "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-24.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 511 B | 
| Before Width: | Height: | Size: 245 B After Width: | Height: | Size: 245 B | 
| Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 339 B | 
| Before Width: | Height: | Size: 463 B After Width: | Height: | Size: 463 B | 
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/edit-20.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 312 B | 
| Before Width: | Height: | Size: 288 B After Width: | Height: | Size: 288 B | 
| Before Width: | Height: | Size: 284 B After Width: | Height: | Size: 284 B | 
| Before Width: | Height: | Size: 292 B After Width: | Height: | Size: 292 B | 
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/forward-24.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 511 B | 
| Before Width: | Height: | Size: 155 B After Width: | Height: | Size: 155 B | 
| Before Width: | Height: | Size: 323 B | 
| Before Width: | Height: | Size: 358 B After Width: | Height: | Size: 358 B | 
| Before Width: | Height: | Size: 252 B After Width: | Height: | Size: 252 B | 
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/play-20.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 288 B | 
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/save-20.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 388 B | 
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/search-20.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 431 B | 
| Before Width: | Height: | Size: 419 B After Width: | Height: | Size: 419 B | 
| Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 354 B | 
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/shield-20.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 388 B | 
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/shield-off-20.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 462 B | 
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/tree-root-16.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 240 B | 
| Before Width: | Height: | Size: 337 B | 
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/x-20.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 259 B | 
							
								
								
									
										1
									
								
								src/public/images/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"]'); | ||||
|  | ||||
| @@ -45,16 +47,28 @@ async function showDialog() { | ||||
|     $clonePrefix.val(''); | ||||
|     $linkTitle.val(''); | ||||
|  | ||||
|     function setDefaultLinkTitle(noteId) { | ||||
|         const noteTitle = treeUtils.getNoteTitle(noteId); | ||||
|     async function setDefaultLinkTitle(noteId) { | ||||
|         const noteTitle = await treeUtils.getNoteTitle(noteId); | ||||
|  | ||||
|         $linkTitle.val(noteTitle); | ||||
|     } | ||||
|  | ||||
|     $autoComplete.autocomplete({ | ||||
|         source: await autocompleteService.getAutocompleteItems(), | ||||
|         minLength: 0, | ||||
|         change: () => { | ||||
|         source: async function(request, response) { | ||||
|             const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term)); | ||||
|  | ||||
|             if (result.length > 0) { | ||||
|                 response(result); | ||||
|             } | ||||
|             else { | ||||
|                 response([{ | ||||
|                     label: "No results", | ||||
|                     value: "No results" | ||||
|                 }]); | ||||
|             } | ||||
|         }, | ||||
|         minLength: 2, | ||||
|         change: async () => { | ||||
|             const val = $autoComplete.val(); | ||||
|             const notePath = linkService.getNodePathFromLabel(val); | ||||
|             if (!notePath) { | ||||
| @@ -64,16 +78,16 @@ async function showDialog() { | ||||
|             const noteId = treeUtils.getNoteIdFromNotePath(notePath); | ||||
|  | ||||
|             if (noteId) { | ||||
|                 setDefaultLinkTitle(noteId); | ||||
|                 await setDefaultLinkTitle(noteId); | ||||
|             } | ||||
|         }, | ||||
|         // this is called when user goes through autocomplete list with keyboard | ||||
|         // at this point the item isn't selected yet so we use supplied ui.item to see WHERE the cursor is | ||||
|         focus: (event, ui) => { | ||||
|         focus: async (event, ui) => { | ||||
|             const notePath = linkService.getNodePathFromLabel(ui.item.value); | ||||
|             const noteId = treeUtils.getNoteIdFromNotePath(notePath); | ||||
|  | ||||
|             setDefaultLinkTitle(noteId); | ||||
|             await setDefaultLinkTitle(noteId); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| @@ -92,7 +106,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 +136,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); | ||||
|   | ||||
| @@ -25,7 +25,7 @@ async function showDialog() { | ||||
|  | ||||
|     $treePrefixInput.val(branch.prefix).focus(); | ||||
|  | ||||
|     const noteTitle = treeUtils.getNoteTitle(currentNode.data.noteId); | ||||
|     const noteTitle = await treeUtils.getNoteTitle(currentNode.data.noteId); | ||||
|  | ||||
|     $noteTitle.html(noteTitle); | ||||
| } | ||||
|   | ||||
| @@ -19,10 +19,10 @@ 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 = linkService.createNoteLink(event.noteId).prop('outerHTML'); | ||||
|             const noteLink = await linkService.createNoteLink(event.noteId).prop('outerHTML'); | ||||
|  | ||||
|             event.comment = event.comment.replace('<note>', noteLink); | ||||
|         } | ||||
|   | ||||
| @@ -1,11 +1,13 @@ | ||||
| import treeService from '../services/tree.js'; | ||||
| import linkService from '../services/link.js'; | ||||
| import utils from '../services/utils.js'; | ||||
| import autocompleteService from '../services/autocomplete.js'; | ||||
| import server from '../services/server.js'; | ||||
| import searchNotesService from '../services/search_notes.js'; | ||||
|  | ||||
| const $dialog = $("#jump-to-note-dialog"); | ||||
| const $autoComplete = $("#jump-to-note-autocomplete"); | ||||
| const $form = $("#jump-to-note-form"); | ||||
| const $jumpToNoteButton = $("#jump-to-note-button"); | ||||
| const $showInFullTextButton = $("#show-in-full-text-button"); | ||||
|  | ||||
| async function showDialog() { | ||||
|     glob.activeDialog = $dialog; | ||||
| @@ -18,8 +20,23 @@ async function showDialog() { | ||||
|     }); | ||||
|  | ||||
|     await $autoComplete.autocomplete({ | ||||
|         source: await utils.stopWatch("building autocomplete", autocompleteService.getAutocompleteItems), | ||||
|         minLength: 1 | ||||
|         source: async function(request, response) { | ||||
|             const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term)); | ||||
|  | ||||
|             if (result.length > 0) { | ||||
|                 response(result); | ||||
|             } | ||||
|             else { | ||||
|                 response([{ | ||||
|                     label: "No results", | ||||
|                     value: "No results" | ||||
|                 }]); | ||||
|             } | ||||
|         }, | ||||
|         focus: function(event, ui) { | ||||
|             return $(ui.item).val() !== 'No results'; | ||||
|         }, | ||||
|         minLength: 2 | ||||
|     }); | ||||
| } | ||||
|  | ||||
| @@ -38,12 +55,32 @@ function goToNote() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| function showInFullText(e) { | ||||
|     // stop from propagating upwards (dangerous especially with ctrl+enter executable javascript notes) | ||||
|     e.preventDefault(); | ||||
|     e.stopPropagation(); | ||||
|  | ||||
|     const searchText = $autoComplete.val(); | ||||
|  | ||||
|     searchNotesService.resetSearch(); | ||||
|     searchNotesService.showSearch(); | ||||
|     searchNotesService.doSearch(searchText); | ||||
|  | ||||
|     $dialog.dialog('close'); | ||||
| } | ||||
|  | ||||
| $form.submit(() => { | ||||
|     goToNote(); | ||||
|  | ||||
|     return false; | ||||
| }); | ||||
|  | ||||
| $jumpToNoteButton.click(goToNote); | ||||
|  | ||||
| $showInFullTextButton.click(showInFullText); | ||||
|  | ||||
| $dialog.bind('keydown', 'ctrl+return', showInFullText); | ||||
|  | ||||
| export default { | ||||
|     showDialog | ||||
| }; | ||||
| @@ -54,7 +54,13 @@ $list.on('change', () => { | ||||
|     const revisionItem = revisionItems.find(r => r.noteRevisionId === optVal); | ||||
|  | ||||
|     $title.html(revisionItem.title); | ||||
|     $content.html(revisionItem.content); | ||||
|  | ||||
|     if (revisionItem.type === 'text') { | ||||
|         $content.html(revisionItem.content); | ||||
|     } | ||||
|     else if (revisionItem.type === 'code') { | ||||
|         $content.html($("<pre>").text(revisionItem.content)); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| $(document).on('click', "a[action='note-revision']", event => { | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| "use strict"; | ||||
|  | ||||
| import protectedSessionHolder from '../services/protected_session_holder.js'; | ||||
| import utils from '../services/utils.js'; | ||||
| import server from '../services/server.js'; | ||||
| import infoService from "../services/info.js"; | ||||
| import zoomService from "../services/zoom.js"; | ||||
| import utils from "../services/utils.js"; | ||||
|  | ||||
| const $dialog = $("#options-dialog"); | ||||
| const $tabs = $("#options-tabs"); | ||||
| @@ -44,6 +45,41 @@ export default { | ||||
|     saveOptions | ||||
| }; | ||||
|  | ||||
| addTabHandler((function() { | ||||
|     const $themeSelect = $("#theme-select"); | ||||
|     const $zoomFactorSelect = $("#zoom-factor-select"); | ||||
|     const $html = $("html"); | ||||
|  | ||||
|     function optionsLoaded(options) { | ||||
|         $themeSelect.val(options.theme); | ||||
|  | ||||
|         if (utils.isElectron()) { | ||||
|             $zoomFactorSelect.val(options.zoomFactor); | ||||
|         } | ||||
|         else { | ||||
|             $zoomFactorSelect.prop('disabled', true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     $themeSelect.change(function() { | ||||
|         const newTheme = $(this).val(); | ||||
|  | ||||
|         $html.attr("class", "theme-" + newTheme); | ||||
|  | ||||
|         server.put('options/theme/' + newTheme); | ||||
|     }); | ||||
|  | ||||
|     $zoomFactorSelect.change(function() { | ||||
|         const newZoomFactor = $(this).val(); | ||||
|  | ||||
|         zoomService.setZoomFactorAndSave(newZoomFactor); | ||||
|     }); | ||||
|  | ||||
|     return { | ||||
|         optionsLoaded | ||||
|     }; | ||||
| })()); | ||||
|  | ||||
| addTabHandler((function() { | ||||
|     const $form = $("#change-password-form"); | ||||
|     const $oldPassword = $("#old-password"); | ||||
| @@ -137,6 +173,7 @@ addTabHandler((function () { | ||||
| addTabHandler((async function () { | ||||
|     const $appVersion = $("#app-version"); | ||||
|     const $dbVersion = $("#db-version"); | ||||
|     const $syncVersion = $("#sync-version"); | ||||
|     const $buildDate = $("#build-date"); | ||||
|     const $buildRevision = $("#build-revision"); | ||||
|  | ||||
| @@ -144,6 +181,7 @@ addTabHandler((async function () { | ||||
|  | ||||
|     $appVersion.html(appInfo.appVersion); | ||||
|     $dbVersion.html(appInfo.dbVersion); | ||||
|     $syncVersion.html(appInfo.syncVersion); | ||||
|     $buildDate.html(appInfo.buildDate); | ||||
|     $buildRevision.html(appInfo.buildRevision); | ||||
|     $buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision); | ||||
|   | ||||
| @@ -40,7 +40,7 @@ async function showDialog() { | ||||
|                 noteLink = change.current_title; | ||||
|             } | ||||
|             else { | ||||
|                 noteLink = linkService.createNoteLink(change.noteId, change.title); | ||||
|                 noteLink = await linkService.createNoteLink(change.noteId, change.title); | ||||
|             } | ||||
|  | ||||
|             changesListEl.append($('<li>') | ||||
|   | ||||
| @@ -1,47 +1,18 @@ | ||||
| import treeService from '../services/tree.js'; | ||||
| import messagingService from '../services/messaging.js'; | ||||
| import server from '../services/server.js'; | ||||
| import utils from "../services/utils.js"; | ||||
| import treeUtils from "../services/tree_utils.js"; | ||||
|  | ||||
| const $dialog = $("#recent-notes-dialog"); | ||||
| const $searchInput = $('#recent-notes-search-input'); | ||||
|  | ||||
| // list of recent note paths | ||||
| let list = []; | ||||
|  | ||||
| async function reload() { | ||||
|     const result = await server.get('recent-notes'); | ||||
|  | ||||
|     list = result.map(r => r.notePath); | ||||
| } | ||||
|  | ||||
| function addRecentNote(branchId, notePath) { | ||||
|     setTimeout(async () => { | ||||
|         // we include the note into recent list only if the user stayed on the note at least 5 seconds | ||||
|         if (notePath && notePath === treeService.getCurrentNotePath()) { | ||||
|             const result = await server.put('recent-notes/' + branchId + '/' + encodeURIComponent(notePath)); | ||||
|  | ||||
|             list = result.map(r => r.notePath); | ||||
|         } | ||||
|     }, 1500); | ||||
| } | ||||
|  | ||||
| async function getNoteTitle(notePath) { | ||||
|     let noteTitle; | ||||
|  | ||||
|     try { | ||||
|         noteTitle = await treeUtils.getNotePathTitle(notePath); | ||||
|     } | ||||
|     catch (e) { | ||||
|         noteTitle = "[error - can't find note title]"; | ||||
|  | ||||
|         messagingService.logError("Could not find title for notePath=" + notePath + ", stack=" + e.stack); | ||||
|     } | ||||
|  | ||||
|     return noteTitle; | ||||
| } | ||||
|  | ||||
| async function showDialog() { | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
| @@ -54,16 +25,17 @@ async function showDialog() { | ||||
|  | ||||
|     $searchInput.val(''); | ||||
|  | ||||
|     // remove the current note | ||||
|     const recNotes = list.filter(note => note !== treeService.getCurrentNotePath()); | ||||
|     const items = []; | ||||
|     const result = await server.get('recent-notes'); | ||||
|  | ||||
|     for (const notePath of recNotes) { | ||||
|         items.push({ | ||||
|             label: await getNoteTitle(notePath), | ||||
|             value: notePath | ||||
|         }); | ||||
|     } | ||||
|     // remove the current note | ||||
|     const recNotes = result.filter(note => note.notePath !== treeService.getCurrentNotePath()); | ||||
|  | ||||
|     const items = recNotes.map(rn => { | ||||
|         return { | ||||
|             label: rn.title, | ||||
|             value: rn.notePath | ||||
|         }; | ||||
|     }); | ||||
|  | ||||
|     $searchInput.autocomplete({ | ||||
|         source: items, | ||||
| @@ -96,18 +68,7 @@ async function showDialog() { | ||||
|     }); | ||||
| } | ||||
|  | ||||
| setTimeout(reload, 100); | ||||
|  | ||||
| messagingService.subscribeToMessages(syncData => { | ||||
|     if (syncData.some(sync => sync.entityName === 'recent_notes')) { | ||||
|         console.log(utils.now(), "Reloading recent notes because of background changes"); | ||||
|  | ||||
|         reload(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| export default { | ||||
|     showDialog, | ||||
|     addRecentNote, | ||||
|     reload | ||||
|     addRecentNote | ||||
| }; | ||||
| @@ -14,6 +14,10 @@ class Branch { | ||||
|         return await this.treeCache.getNote(this.noteId); | ||||
|     } | ||||
|  | ||||
|     isTopLevel() { | ||||
|         return this.parentNoteId === 'root'; | ||||
|     } | ||||
|  | ||||
|     get toString() { | ||||
|         return `Branch(branchId=${this.branchId})`; | ||||
|     } | ||||
|   | ||||
| @@ -6,8 +6,11 @@ class NoteFull extends NoteShort { | ||||
|  | ||||
|         this.content = row.content; | ||||
|  | ||||
|         if (this.isJson()) { | ||||
|             this.jsonContent = JSON.parse(this.content); | ||||
|         if (this.content !== "" && this.isJson()) { | ||||
|             try { | ||||
|                 this.jsonContent = JSON.parse(this.content); | ||||
|             } | ||||
|             catch(e) {} | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -6,7 +6,7 @@ class NoteShort { | ||||
|         this.isProtected = row.isProtected; | ||||
|         this.type = row.type; | ||||
|         this.mime = row.mime; | ||||
|         this.hideInAutocomplete = row.hideInAutocomplete; | ||||
|         this.archived = row.archived; | ||||
|     } | ||||
|  | ||||
|     isJson() { | ||||
| @@ -14,36 +14,55 @@ class NoteShort { | ||||
|     } | ||||
|  | ||||
|     async getBranches() { | ||||
|         const branches = []; | ||||
|         const branchIds = this.treeCache.parents[this.noteId].map( | ||||
|             parentNoteId => this.treeCache.getBranchIdByChildParent(this.noteId, parentNoteId)); | ||||
|  | ||||
|         for (const parent of this.treeCache.parents[this.noteId]) { | ||||
|             branches.push(await this.treeCache.getBranchByChildParent(this.noteId, parent.noteId)); | ||||
|         } | ||||
|         return this.treeCache.getBranches(branchIds); | ||||
|     } | ||||
|  | ||||
|         return branches; | ||||
|     hasChildren() { | ||||
|         return this.treeCache.children[this.noteId] | ||||
|             && this.treeCache.children[this.noteId].length > 0; | ||||
|     } | ||||
|  | ||||
|     async getChildBranches() { | ||||
|         const branches = []; | ||||
|  | ||||
|         for (const child of this.treeCache.children[this.noteId]) { | ||||
|             branches.push(await this.treeCache.getBranchByChildParent(child.noteId, this.noteId)); | ||||
|         if (!this.treeCache.children[this.noteId]) { | ||||
|             return []; | ||||
|         } | ||||
|  | ||||
|         return branches; | ||||
|         const branchIds = this.treeCache.children[this.noteId].map( | ||||
|             childNoteId => this.treeCache.getBranchIdByChildParent(childNoteId, this.noteId)); | ||||
|  | ||||
|         return await this.treeCache.getBranches(branchIds); | ||||
|     } | ||||
|  | ||||
|     async getParentNotes() { | ||||
|     getParentNoteIds() { | ||||
|         return this.treeCache.parents[this.noteId] || []; | ||||
|     } | ||||
|  | ||||
|     async getChildNotes() { | ||||
|     async getParentNotes() { | ||||
|         return await this.treeCache.getNotes(this.getParentNoteIds()); | ||||
|     } | ||||
|  | ||||
|     getChildNoteIds() { | ||||
|         return this.treeCache.children[this.noteId] || []; | ||||
|     } | ||||
|  | ||||
|     async getChildNotes() { | ||||
|         return await this.treeCache.getNotes(this.getChildNoteIds()); | ||||
|     } | ||||
|  | ||||
|     get toString() { | ||||
|         return `Note(noteId=${this.noteId}, title=${this.title})`; | ||||
|     } | ||||
|  | ||||
|     get dto() { | ||||
|         const dto = Object.assign({}, this); | ||||
|         delete dto.treeCache; | ||||
|         delete dto.archived; | ||||
|  | ||||
|         return dto; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default NoteShort; | ||||
| @@ -1,40 +0,0 @@ | ||||
| import server from './services/server.js'; | ||||
|  | ||||
| $(document).ready(() => { | ||||
|     server.get('migration').then(result => { | ||||
|         const appDbVersion = result.app_dbVersion; | ||||
|         const dbVersion = result.dbVersion; | ||||
|  | ||||
|         if (appDbVersion === dbVersion) { | ||||
|             $("#up-to-date").show(); | ||||
|         } | ||||
|         else { | ||||
|             $("#need-to-migrate").show(); | ||||
|  | ||||
|             $("#app-db-version").html(appDbVersion); | ||||
|             $("#db-version").html(dbVersion); | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| $("#run-migration").click(async () => { | ||||
|     $("#run-migration").prop("disabled", true); | ||||
|  | ||||
|     $("#migration-result").show(); | ||||
|  | ||||
|     const result = await server.post('migration'); | ||||
|  | ||||
|     for (const migration of result.migrations) { | ||||
|         const row = $('<tr>') | ||||
|             .append($('<td>').html(migration.dbVersion)) | ||||
|             .append($('<td>').html(migration.name)) | ||||
|             .append($('<td>').html(migration.success ? 'Yes' : 'No')) | ||||
|             .append($('<td>').html(migration.success ? 'N/A' : migration.error)); | ||||
|  | ||||
|         if (!migration.success) { | ||||
|             row.addClass("danger"); | ||||
|         } | ||||
|  | ||||
|         $("#migration-table").append(row); | ||||
|     } | ||||
| }); | ||||
| @@ -1,104 +0,0 @@ | ||||
| import treeCache from "./tree_cache.js"; | ||||
| import treeUtils from "./tree_utils.js"; | ||||
|  | ||||
| async function getAutocompleteItems(parentNoteId, notePath, titlePath) { | ||||
|     if (!parentNoteId) { | ||||
|         parentNoteId = 'root'; | ||||
|     } | ||||
|  | ||||
|     const parentNote = await treeCache.getNote(parentNoteId); | ||||
|     const childNotes = await parentNote.getChildNotes(); | ||||
|  | ||||
|     if (!childNotes.length) { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     if (!notePath) { | ||||
|         notePath = ''; | ||||
|     } | ||||
|  | ||||
|     if (!titlePath) { | ||||
|         titlePath = ''; | ||||
|     } | ||||
|  | ||||
|     // https://github.com/zadam/trilium/issues/46 | ||||
|     // unfortunately not easy to implement because we don't have an easy access to note's isProtected property | ||||
|  | ||||
|     const autocompleteItems = []; | ||||
|  | ||||
|     for (const childNote of childNotes) { | ||||
|         if (childNote.hideInAutocomplete) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         const childNotePath = (notePath ? (notePath + '/') : '') + childNote.noteId; | ||||
|         const childTitlePath = (titlePath ? (titlePath + ' / ') : '') + await treeUtils.getNoteTitle(childNote.noteId, parentNoteId); | ||||
|  | ||||
|         autocompleteItems.push({ | ||||
|             value: childTitlePath + ' (' + childNotePath + ')', | ||||
|             label: childTitlePath | ||||
|         }); | ||||
|  | ||||
|         const childItems = await getAutocompleteItems(childNote.noteId, childNotePath, childTitlePath); | ||||
|  | ||||
|         for (const childItem of childItems) { | ||||
|             autocompleteItems.push(childItem); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (parentNoteId === 'root') { | ||||
|         console.log(`Generated ${autocompleteItems.length} autocomplete items`); | ||||
|     } | ||||
|  | ||||
|     return autocompleteItems; | ||||
| } | ||||
|  | ||||
| // Overrides the default autocomplete filter function to search for matched on atleast 1 word in each of the input term's words | ||||
| $.ui.autocomplete.filter = (array, terms) => { | ||||
|     if (!terms) { | ||||
|         return array; | ||||
|     } | ||||
|  | ||||
|     const startDate = new Date(); | ||||
|  | ||||
|     const results = []; | ||||
|     const tokens = terms.toLowerCase().split(" "); | ||||
|  | ||||
|     for (const item of array) { | ||||
|         const lcLabel = item.label.toLowerCase(); | ||||
|  | ||||
|         const found = tokens.every(token => lcLabel.indexOf(token) !== -1); | ||||
|         if (!found) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         // this is not completely correct and might cause minor problems with note with names containing this " / " | ||||
|         const lastSegmentIndex = lcLabel.lastIndexOf(" / "); | ||||
|  | ||||
|         if (lastSegmentIndex !== -1) { | ||||
|             const lastSegment = lcLabel.substr(lastSegmentIndex + 3); | ||||
|  | ||||
|             // at least some token needs to be in the last segment (leaf note), otherwise this | ||||
|             // particular note is not that interesting (query is satisfied by parent note) | ||||
|             const foundInLastSegment = tokens.some(token => lastSegment.indexOf(token) !== -1); | ||||
|  | ||||
|             if (!foundInLastSegment) { | ||||
|                 continue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         results.push(item); | ||||
|  | ||||
|         if (results.length > 100) { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     console.log("Search took " + (new Date().getTime() - startDate.getTime()) + "ms"); | ||||
|  | ||||
|     return results; | ||||
| }; | ||||
|  | ||||
| export default { | ||||
|     getAutocompleteItems | ||||
| }; | ||||
							
								
								
									
										3
									
								
								src/public/javascripts/services/bootstrap.js
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -17,7 +17,7 @@ import messagingService from './messaging.js'; | ||||
| import noteDetailService from './note_detail.js'; | ||||
| import noteType from './note_type.js'; | ||||
| import protected_session from './protected_session.js'; | ||||
| import searchTreeService from './search_tree.js'; | ||||
| import searchNotesService from './search_notes.js'; | ||||
| import ScriptApi from './script_api.js'; | ||||
| import ScriptContext from './script_context.js'; | ||||
| import sync from './sync.js'; | ||||
| @@ -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; | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import treeUtils from './tree_utils.js'; | ||||
| import branchPrefixDialog from '../dialogs/branch_prefix.js'; | ||||
| import infoService from "./info.js"; | ||||
| import treeCache from "./tree_cache.js"; | ||||
| import syncService from "./sync.js"; | ||||
|  | ||||
| const $tree = $("#tree"); | ||||
|  | ||||
| @@ -93,27 +94,34 @@ 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(branchId); | ||||
|         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 && parentNote.type !== 'search'); | ||||
|         $tree.contextmenu("enableEntry", "copy", isNotRoot); | ||||
|         $tree.contextmenu("enableEntry", "cut", isNotRoot); | ||||
|         $tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0 && isNotRoot && parentNote.type !== 'search'); | ||||
|         $tree.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0 && note.type !== 'search'); | ||||
|         $tree.contextmenu("enableEntry", "importBranch", note.type !== 'search'); | ||||
|         $tree.contextmenu("enableEntry", "exportBranch", note.type !== 'search'); | ||||
|         $tree.contextmenu("enableEntry", "editBranchPrefix", parentNote.type !== 'search'); | ||||
|  | ||||
|         // Activate node on right-click | ||||
|         node.setActive(); | ||||
| @@ -158,8 +166,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); | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import utils from "./utils.js"; | ||||
| import treeService from "./tree.js"; | ||||
| import linkService from "./link.js"; | ||||
| import fileService from "./file.js"; | ||||
| import zoomService from "./zoom.js"; | ||||
| import noteRevisionsDialog from "../dialogs/note_revisions.js"; | ||||
| import optionsDialog from "../dialogs/options.js"; | ||||
| import addLinkDialog from "../dialogs/add_link.js"; | ||||
| @@ -10,8 +11,9 @@ import jumpToNoteDialog from "../dialogs/jump_to_note.js"; | ||||
| import noteSourceDialog from "../dialogs/note_source.js"; | ||||
| import recentChangesDialog from "../dialogs/recent_changes.js"; | ||||
| import sqlConsoleDialog from "../dialogs/sql_console.js"; | ||||
| import searchTreeService from "./search_tree.js"; | ||||
| import searchNotesService from "./search_notes.js"; | ||||
| import labelsDialog from "../dialogs/labels.js"; | ||||
| import protectedSessionService from "./protected_session.js"; | ||||
|  | ||||
| function registerEntrypoints() { | ||||
|     // hot keys are active also inside inputs and content editables | ||||
| @@ -21,7 +23,7 @@ function registerEntrypoints() { | ||||
|  | ||||
|     utils.bindShortcut('ctrl+l', addLinkDialog.showDialog); | ||||
|  | ||||
|     $("#jump-to-note-button").click(jumpToNoteDialog.showDialog); | ||||
|     $("#jump-to-note-dialog-button").click(jumpToNoteDialog.showDialog); | ||||
|     utils.bindShortcut('ctrl+j', jumpToNoteDialog.showDialog); | ||||
|  | ||||
|     $("#show-note-revisions-button").click(noteRevisionsDialog.showCurrentNoteRevisions); | ||||
| @@ -31,11 +33,14 @@ 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); | ||||
|  | ||||
|     $("#toggle-search-button").click(searchTreeService.toggleSearch); | ||||
|     utils.bindShortcut('ctrl+s', searchTreeService.toggleSearch); | ||||
|     $("#toggle-search-button").click(searchNotesService.toggleSearch); | ||||
|     utils.bindShortcut('ctrl+s', searchNotesService.toggleSearch); | ||||
|  | ||||
|     $(".show-labels-button").click(labelsDialog.showDialog); | ||||
|     utils.bindShortcut('alt+l', labelsDialog.showDialog); | ||||
| @@ -45,11 +50,21 @@ 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); | ||||
|     } | ||||
|  | ||||
|     utils.bindShortcut('alt+m', e => $(".hide-toggle").toggleClass("suppressed")); | ||||
|     utils.bindShortcut('alt+m', e => { | ||||
|         $(".hide-toggle").toggle(); | ||||
|  | ||||
|         // when hiding switch display to block, otherwise grid still tries to display columns which shows | ||||
|         // left empty column | ||||
|         $("#container").css("display", $("#container").css("display") === "grid" ? "block" : "grid"); | ||||
|     }); | ||||
|  | ||||
|     // hide (toggle) everything except for the note content for distraction free writing | ||||
|     utils.bindShortcut('alt+t', e => { | ||||
| @@ -101,27 +116,10 @@ function registerEntrypoints() { | ||||
|         $("#note-detail-text").focus(); | ||||
|     }); | ||||
|  | ||||
|     $(document).bind('keydown', 'ctrl+-', () => { | ||||
|         if (utils.isElectron()) { | ||||
|             const webFrame = require('electron').webFrame; | ||||
|  | ||||
|             if (webFrame.getZoomFactor() > 0.2) { | ||||
|                 webFrame.setZoomFactor(webFrame.getZoomFactor() - 0.1); | ||||
|             } | ||||
|  | ||||
|             return false; | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     $(document).bind('keydown', 'ctrl+=', () => { | ||||
|         if (utils.isElectron()) { | ||||
|             const webFrame = require('electron').webFrame; | ||||
|  | ||||
|             webFrame.setZoomFactor(webFrame.getZoomFactor() + 0.1); | ||||
|  | ||||
|             return false; | ||||
|         } | ||||
|     }); | ||||
|     if (utils.isElectron()) { | ||||
|         $(document).bind('keydown', 'ctrl+-', zoomService.decreaseZoomFactor); | ||||
|         $(document).bind('keydown', 'ctrl+=', zoomService.increaseZoomFactor); | ||||
|     } | ||||
|  | ||||
|     $("#note-title").bind('keydown', 'return', () => $("#note-detail-text").focus()); | ||||
|  | ||||
|   | ||||
| @@ -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(); | ||||
| }); | ||||
|   | ||||
| @@ -32,18 +32,19 @@ async function requireLibrary(library) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| const dynamicallyLoadedScripts = []; | ||||
| // we save the promises in case of the same script being required concurrently multiple times | ||||
| const loadedScriptPromises = {}; | ||||
|  | ||||
| async function requireScript(url) { | ||||
|     if (!dynamicallyLoadedScripts.includes(url)) { | ||||
|         dynamicallyLoadedScripts.push(url); | ||||
|  | ||||
|         return await $.ajax({ | ||||
|     if (!loadedScriptPromises[url]) { | ||||
|         loadedScriptPromises[url] = $.ajax({ | ||||
|             url: url, | ||||
|             dataType: "script", | ||||
|             cache: true | ||||
|         }) | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     await loadedScriptPromises[url]; | ||||
| } | ||||
|  | ||||
| async function requireCss(url) { | ||||
|   | ||||
| @@ -23,11 +23,11 @@ function getNodePathFromLabel(label) { | ||||
|     return null; | ||||
| } | ||||
|  | ||||
| function createNoteLink(notePath, noteTitle) { | ||||
| async function createNoteLink(notePath, noteTitle) { | ||||
|     if (!noteTitle) { | ||||
|         const noteId = treeUtils.getNoteIdFromNotePath(notePath); | ||||
|  | ||||
|         noteTitle = treeUtils.getNoteTitle(noteId); | ||||
|         noteTitle = await treeUtils.getNoteTitle(noteId); | ||||
|     } | ||||
|  | ||||
|     const noteLink = $("<a>", { | ||||
| @@ -76,9 +76,11 @@ function goToLink(e) { | ||||
|  | ||||
| function addLinkToEditor(linkTitle, linkHref) { | ||||
|     const editor = noteDetailText.getEditor(); | ||||
|     const doc = editor.document; | ||||
|  | ||||
|     doc.enqueueChanges(() => editor.data.insertLink(linkTitle, linkHref), doc.selection); | ||||
|     editor.model.change( writer => { | ||||
|         const insertPosition = editor.model.document.selection.getFirstPosition(); | ||||
|         writer.insertText(linkTitle, { linkHref: linkHref }, insertPosition); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function addTextToEditor(text) { | ||||
|   | ||||
| @@ -100,7 +100,7 @@ setTimeout(() => { | ||||
|             lastSyncId: lastSyncId | ||||
|         })); | ||||
|     }, 1000); | ||||
| }, 1000); | ||||
| }, 0); | ||||
|  | ||||
| export default { | ||||
|     logError, | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import treeService from './tree.js'; | ||||
| import treeUtils from './tree_utils.js'; | ||||
| import noteTypeService from './note_type.js'; | ||||
| import protectedSessionService from './protected_session.js'; | ||||
| import protectedSessionHolder from './protected_session_holder.js'; | ||||
| @@ -21,9 +22,11 @@ const $noteDetailComponents = $(".note-detail-component"); | ||||
| const $protectButton = $("#protect-button"); | ||||
| const $unprotectButton = $("#unprotect-button"); | ||||
| const $noteDetailWrapper = $("#note-detail-wrapper"); | ||||
| const $noteDetailComponentWrapper = $("#note-detail-component-wrapper"); | ||||
| const $noteIdDisplay = $("#note-id-display"); | ||||
| const $labelList = $("#label-list"); | ||||
| const $labelListInner = $("#label-list-inner"); | ||||
| const $childrenOverview = $("#children-overview"); | ||||
|  | ||||
| let currentNote = null; | ||||
|  | ||||
| @@ -73,58 +76,50 @@ function noteChanged() { | ||||
| async function reload() { | ||||
|     // no saving here | ||||
|  | ||||
|     await loadNoteToEditor(getCurrentNoteId()); | ||||
|     await loadNoteDetail(getCurrentNoteId()); | ||||
| } | ||||
|  | ||||
| async function switchToNote(noteId) { | ||||
|     if (getCurrentNoteId() !== noteId) { | ||||
|         await saveNoteIfChanged(); | ||||
|  | ||||
|         await loadNoteToEditor(noteId); | ||||
|         await loadNoteDetail(noteId); | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function saveNote() { | ||||
|     const note = getCurrentNote(); | ||||
|  | ||||
|     note.title = $noteTitle.val(); | ||||
|     note.content = getComponent(note.type).getContent(); | ||||
|  | ||||
|     treeService.setNoteTitle(note.noteId, note.title); | ||||
|  | ||||
|     await server.put('notes/' + note.noteId, note.dto); | ||||
|  | ||||
|     isNoteChanged = false; | ||||
|  | ||||
|     if (note.isProtected) { | ||||
|         protectedSessionHolder.touchProtectedSession(); | ||||
|     } | ||||
|  | ||||
|     infoService.showMessage("Saved!"); | ||||
| } | ||||
|  | ||||
| async function saveNoteIfChanged() { | ||||
|     if (!isNoteChanged) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const note = getCurrentNote(); | ||||
|  | ||||
|     updateNoteFromInputs(note); | ||||
|  | ||||
|     await saveNoteToServer(note); | ||||
|  | ||||
|     if (note.isProtected) { | ||||
|         protectedSessionHolder.touchProtectedSession(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function updateNoteFromInputs(note) { | ||||
|     note.title = $noteTitle.val(); | ||||
|     note.content = getComponent(note.type).getContent(); | ||||
|  | ||||
|     treeService.setNoteTitle(note.noteId, note.title); | ||||
| } | ||||
|  | ||||
| async function saveNoteToServer(note) { | ||||
|     const dto = Object.assign({}, note); | ||||
|     delete dto.treeCache; | ||||
|     delete dto.hideInAutocomplete; | ||||
|  | ||||
|     await server.put('notes/' + dto.noteId, dto); | ||||
|  | ||||
|     isNoteChanged = false; | ||||
|  | ||||
|     infoService.showMessage("Saved!"); | ||||
|     await saveNote(); | ||||
| } | ||||
|  | ||||
| function setNoteBackgroundIfProtected(note) { | ||||
|     const isProtected = !!note.isProtected; | ||||
|  | ||||
|     $noteDetailWrapper.toggleClass("protected", isProtected); | ||||
|     $protectButton.toggle(!isProtected); | ||||
|     $unprotectButton.toggle(isProtected); | ||||
|     $noteDetailComponentWrapper.toggleClass("protected", isProtected); | ||||
|     $protectButton.toggleClass("active", isProtected); | ||||
|     $unprotectButton.toggleClass("active", !isProtected); | ||||
| } | ||||
|  | ||||
| let isNewNoteCreated = false; | ||||
| @@ -145,7 +140,7 @@ async function handleProtectedSession() { | ||||
|     protectedSessionService.ensureDialogIsClosed(); | ||||
| } | ||||
|  | ||||
| async function loadNoteToEditor(noteId) { | ||||
| async function loadNoteDetail(noteId) { | ||||
|     currentNote = await loadNote(noteId); | ||||
|  | ||||
|     if (isNewNoteCreated) { | ||||
| @@ -156,6 +151,8 @@ async function loadNoteToEditor(noteId) { | ||||
|  | ||||
|     $noteIdDisplay.html(noteId); | ||||
|  | ||||
|     setNoteBackgroundIfProtected(currentNote); | ||||
|  | ||||
|     await handleProtectedSession(); | ||||
|  | ||||
|     $noteDetailWrapper.show(); | ||||
| @@ -176,13 +173,40 @@ async function loadNoteToEditor(noteId) { | ||||
|         noteChangeDisabled = false; | ||||
|     } | ||||
|  | ||||
|     setNoteBackgroundIfProtected(currentNote); | ||||
|     treeService.setBranchBackgroundBasedOnProtectedStatus(noteId); | ||||
|  | ||||
|     // after loading new note make sure editor is scrolled to the top | ||||
|     $noteDetailWrapper.scrollTop(0); | ||||
|  | ||||
|     await loadLabelList(); | ||||
|     const labels = await loadLabelList(); | ||||
|  | ||||
|     const hideChildrenOverview = labels.some(label => label.name === 'hideChildrenOverview'); | ||||
|     await showChildrenOverview(hideChildrenOverview); | ||||
| } | ||||
|  | ||||
| async function showChildrenOverview(hideChildrenOverview) { | ||||
|     if (hideChildrenOverview) { | ||||
|         $childrenOverview.hide(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const note = getCurrentNote(); | ||||
|  | ||||
|     $childrenOverview.empty(); | ||||
|  | ||||
|     const notePath = treeService.getCurrentNotePath(); | ||||
|  | ||||
|     for (const childBranch of await note.getChildBranches()) { | ||||
|         const link = $('<a>', { | ||||
|             href: 'javascript:', | ||||
|             text: await treeUtils.getNoteTitle(childBranch.noteId, childBranch.parentNoteId) | ||||
|         }).attr('action', 'note').attr('note-path', notePath + '/' + childBranch.noteId); | ||||
|  | ||||
|         const childEl = $('<div class="child-overview">').html(link); | ||||
|         $childrenOverview.append(childEl); | ||||
|     } | ||||
|  | ||||
|     $childrenOverview.show(); | ||||
| } | ||||
|  | ||||
| async function loadLabelList() { | ||||
| @@ -202,6 +226,8 @@ async function loadLabelList() { | ||||
|     else { | ||||
|         $labelList.hide(); | ||||
|     } | ||||
|  | ||||
|     return labels; | ||||
| } | ||||
|  | ||||
| async function loadNote(noteId) { | ||||
| @@ -245,8 +271,6 @@ setInterval(saveNoteIfChanged, 5000); | ||||
| export default { | ||||
|     reload, | ||||
|     switchToNote, | ||||
|     updateNoteFromInputs, | ||||
|     saveNoteToServer, | ||||
|     setNoteBackgroundIfProtected, | ||||
|     loadNote, | ||||
|     getCurrentNote, | ||||
| @@ -255,6 +279,7 @@ export default { | ||||
|     newNoteCreated, | ||||
|     focus, | ||||
|     loadLabelList, | ||||
|     saveNote, | ||||
|     saveNoteIfChanged, | ||||
|     noteChanged | ||||
| }; | ||||
| @@ -1,4 +1,3 @@ | ||||
| import utils from "./utils.js"; | ||||
| import libraryLoader from "./library_loader.js"; | ||||
| import bundleService from "./bundle.js"; | ||||
| import infoService from "./info.js"; | ||||
| @@ -11,15 +10,19 @@ const $noteDetailCode = $('#note-detail-code'); | ||||
| const $executeScriptButton = $("#execute-script-button"); | ||||
|  | ||||
| async function show() { | ||||
|     if (!codeEditor) { | ||||
|         await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); | ||||
|     await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); | ||||
|  | ||||
|     if (!codeEditor) { | ||||
|         CodeMirror.keyMap.default["Shift-Tab"] = "indentLess"; | ||||
|         CodeMirror.keyMap.default["Tab"] = "indentMore"; | ||||
|  | ||||
|         // these conflict with backward/forward navigation shortcuts | ||||
|         delete CodeMirror.keyMap.default["Alt-Left"]; | ||||
|         delete CodeMirror.keyMap.default["Alt-Right"]; | ||||
|  | ||||
|         CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js'; | ||||
|  | ||||
|         codeEditor = CodeMirror($("#note-detail-code")[0], { | ||||
|         codeEditor = CodeMirror($noteDetailCode[0], { | ||||
|             value: "", | ||||
|             viewportMargin: Infinity, | ||||
|             indentUnit: 4, | ||||
| @@ -28,7 +31,8 @@ async function show() { | ||||
|             highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false}, | ||||
|             lint: true, | ||||
|             gutters: ["CodeMirror-lint-markers"], | ||||
|             lineNumbers: true | ||||
|             lineNumbers: true, | ||||
|             tabindex: 2 // so that tab from title will lead to code editor focus | ||||
|         }); | ||||
|  | ||||
|         codeEditor.on('change', noteDetailService.noteChanged); | ||||
| @@ -38,7 +42,7 @@ async function show() { | ||||
|  | ||||
|     const currentNote = noteDetailService.getCurrentNote(); | ||||
|  | ||||
|     // this needs to happen after the element is shown, otherwise the editor won't be refresheds | ||||
|     // this needs to happen after the element is shown, otherwise the editor won't be refreshed | ||||
|     codeEditor.setValue(currentNote.content); | ||||
|  | ||||
|     const info = CodeMirror.findModeByMIME(currentNote.mime); | ||||
| @@ -60,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/' + getCurrentNoteId()); | ||||
|  | ||||
|             bundleService.executeBundle(bundle); | ||||
|         } | ||||
|  | ||||
|         if (currentNote.mime.endsWith("env=backend")) { | ||||
|             await server.post('script/run/' + 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 | ||||
| } | ||||
| @@ -11,11 +11,16 @@ async function show() { | ||||
|  | ||||
|         textEditor = await BalloonEditor.create($noteDetailText[0], {}); | ||||
|  | ||||
|         textEditor.document.on('change', noteDetailService.noteChanged); | ||||
|         textEditor.model.document.on('change', () => { | ||||
|                 // change is triggered on just marker/selection changes which is not interesting for us | ||||
|                 if (textEditor.model.document.differ.getChanges().length > 0) { | ||||
|                     noteDetailService.noteChanged(); | ||||
|                 } | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     // temporary workaround for https://github.com/ckeditor/ckeditor5-enter/issues/49 | ||||
|     textEditor.setData(noteDetailService.getCurrentNote().content || "<p></p>"); | ||||
|     textEditor.setData(noteDetailService.getCurrentNote().content); | ||||
|  | ||||
|     $noteDetailText.show(); | ||||
| } | ||||
|   | ||||
| @@ -1,9 +1,12 @@ | ||||
| import treeService from './tree.js'; | ||||
| import noteDetail from './note_detail.js'; | ||||
| import noteDetailService from './note_detail.js'; | ||||
| 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() { | ||||
| @@ -84,13 +87,13 @@ function NoteTypeModel() { | ||||
|     }; | ||||
|  | ||||
|     async function save() { | ||||
|         const note = noteDetail.getCurrentNote(); | ||||
|         const note = noteDetailService.getCurrentNote(); | ||||
|  | ||||
|         await server.put('notes/' + note.noteId | ||||
|             + '/type/' + encodeURIComponent(self.type()) | ||||
|             + '/mime/' + encodeURIComponent(self.mime())); | ||||
|  | ||||
|         await noteDetail.reload(); | ||||
|         await noteDetailService.reload(); | ||||
|  | ||||
|         // for the note icon to be updated in the tree | ||||
|         await treeService.reload(); | ||||
| @@ -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'); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										9
									
								
								src/public/javascripts/services/options_init.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | ||||
| import server from "./server.js"; | ||||
|  | ||||
| const optionsReady = new Promise((resolve, reject) => { | ||||
|     $(document).ready(() => server.get('options').then(resolve)); | ||||
| }); | ||||
|  | ||||
| export default { | ||||
|     optionsReady | ||||
| } | ||||
| @@ -1,5 +1,5 @@ | ||||
| import treeService from './tree.js'; | ||||
| import noteDetail from './note_detail.js'; | ||||
| import noteDetailService from './note_detail.js'; | ||||
| import utils from './utils.js'; | ||||
| import server from './server.js'; | ||||
| import protectedSessionHolder from './protected_session_holder.js'; | ||||
| @@ -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(); | ||||
|  | ||||
| @@ -25,7 +39,10 @@ function ensureProtectedSession(requireProtectedSession, modal) { | ||||
|         } | ||||
|  | ||||
|         $dialog.dialog({ | ||||
|             modal: modal, | ||||
|             // modal: modal, | ||||
|             // everything is now non-modal, because modal dialog caused weird high CPU usage on opening | ||||
|             // and tearing of text input | ||||
|             modal: false, | ||||
|             width: 400, | ||||
|             open: () => { | ||||
|                 if (!modal) { | ||||
| @@ -46,7 +63,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."); | ||||
| @@ -57,7 +74,7 @@ async function setupProtectedSession() { | ||||
|  | ||||
|     $dialog.dialog("close"); | ||||
|  | ||||
|     noteDetail.reload(); | ||||
|     noteDetailService.reload(); | ||||
|     treeService.reload(); | ||||
|  | ||||
|     if (protectedSessionDeferred !== null) { | ||||
| @@ -66,8 +83,10 @@ async function setupProtectedSession() { | ||||
|         $noteDetailWrapper.show(); | ||||
|  | ||||
|         protectedSessionDeferred.resolve(); | ||||
|  | ||||
|         protectedSessionDeferred = null; | ||||
|  | ||||
|         $protectedSessionOnButton.addClass('active'); | ||||
|         $protectedSessionOffButton.removeClass('active'); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -81,42 +100,44 @@ function ensureDialogIsClosed() { | ||||
|     $password.val(''); | ||||
| } | ||||
|  | ||||
| async function enterProtectedSession(password) { | ||||
| async function enterProtectedSessionOnServer(password) { | ||||
|     return await server.post('login/protected', { | ||||
|         password: password | ||||
|     }); | ||||
| } | ||||
|  | ||||
| async function protectNoteAndSendToServer() { | ||||
|     if (noteDetailService.getCurrentNote().isProtected) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     await ensureProtectedSession(true, true); | ||||
|  | ||||
|     const note = noteDetail.getCurrentNote(); | ||||
|  | ||||
|     noteDetail.updateNoteFromInputs(note); | ||||
|  | ||||
|     const note = noteDetailService.getCurrentNote(); | ||||
|     note.isProtected = true; | ||||
|  | ||||
|     await noteDetail.saveNoteToServer(note); | ||||
|     await noteDetailService.saveNote(note); | ||||
|  | ||||
|     treeService.setProtected(note.noteId, note.isProtected); | ||||
|  | ||||
|     noteDetail.setNoteBackgroundIfProtected(note); | ||||
|     noteDetailService.setNoteBackgroundIfProtected(note); | ||||
| } | ||||
|  | ||||
| async function unprotectNoteAndSendToServer() { | ||||
|     if (!noteDetailService.getCurrentNote().isProtected) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     await ensureProtectedSession(true, true); | ||||
|  | ||||
|     const note = noteDetail.getCurrentNote(); | ||||
|  | ||||
|     noteDetail.updateNoteFromInputs(note); | ||||
|  | ||||
|     const note = noteDetailService.getCurrentNote(); | ||||
|     note.isProtected = false; | ||||
|  | ||||
|     await noteDetail.saveNoteToServer(note); | ||||
|     await noteDetailService.saveNote(note); | ||||
|  | ||||
|     treeService.setProtected(note.noteId, note.isProtected); | ||||
|  | ||||
|     noteDetail.setNoteBackgroundIfProtected(note); | ||||
|     noteDetailService.setNoteBackgroundIfProtected(note); | ||||
| } | ||||
|  | ||||
| async function protectBranch(noteId, protect) { | ||||
| @@ -127,7 +148,7 @@ async function protectBranch(noteId, protect) { | ||||
|     infoService.showMessage("Request to un/protect sub tree has finished successfully"); | ||||
|  | ||||
|     treeService.reload(); | ||||
|     noteDetail.reload(); | ||||
|     noteDetailService.reload(); | ||||
| } | ||||
|  | ||||
| $passwordForm.submit(() => { | ||||
| @@ -144,5 +165,7 @@ export default { | ||||
|     protectNoteAndSendToServer, | ||||
|     unprotectNoteAndSendToServer, | ||||
|     protectBranch, | ||||
|     ensureDialogIsClosed | ||||
|     ensureDialogIsClosed, | ||||
|     enterProtectedSession, | ||||
|     leaveProtectedSession | ||||
| }; | ||||
| @@ -1,13 +1,11 @@ | ||||
| import utils from "./utils.js"; | ||||
| import server from "./server.js"; | ||||
| import optionsInitService from './options_init.js'; | ||||
|  | ||||
| let lastProtectedSessionOperationDate = null; | ||||
| let protectedSessionTimeout = null; | ||||
| let protectedSessionId = null; | ||||
|  | ||||
| $(document).ready(() => { | ||||
|     server.get('options').then(options => protectedSessionTimeout = options.protectedSessionTimeout); | ||||
| }); | ||||
| optionsInitService.optionsReady.then(options => protectedSessionTimeout = options.protectedSessionTimeout); | ||||
|  | ||||
| setInterval(() => { | ||||
|     if (lastProtectedSessionOperationDate !== null && new Date().getTime() - lastProtectedSessionOperationDate.getTime() > protectedSessionTimeout * 1000) { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import treeService from './tree.js'; | ||||
| import server from './server.js'; | ||||
| import utils from './utils.js'; | ||||
| import infoService from './info.js'; | ||||
|  | ||||
| function ScriptApi(startNote, currentNote) { | ||||
|     const $pluginButtons = $("#plugin-buttons"); | ||||
| @@ -54,7 +55,11 @@ function ScriptApi(startNote, currentNote) { | ||||
|         activateNote, | ||||
|         getInstanceName: () => window.glob.instanceName, | ||||
|         runOnServer, | ||||
|         formatDateISO: utils.formatDateISO | ||||
|         formatDateISO: utils.formatDateISO, | ||||
|         parseDate: utils.parseDate, | ||||
|         showMessage: infoService.showMessage, | ||||
|         showError: infoService.showError, | ||||
|         reloadTree: treeService.reload | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import treeService from './tree.js'; | ||||
| import server from './server.js'; | ||||
| import treeUtils from "./tree_utils.js"; | ||||
| 
 | ||||
| const $tree = $("#tree"); | ||||
| const $searchInput = $("input[name='search-text']"); | ||||
| @@ -7,40 +8,62 @@ const $resetSearchButton = $("#reset-search-button"); | ||||
| const $doSearchButton = $("#do-search-button"); | ||||
| const $saveSearchButton = $("#save-search-button"); | ||||
| const $searchBox = $("#search-box"); | ||||
| const $searchResults = $("#search-results"); | ||||
| const $searchResultsInner = $("#search-results-inner"); | ||||
| const $closeSearchButton = $("#close-search-button"); | ||||
| 
 | ||||
| function showSearch() { | ||||
|     $searchBox.show(); | ||||
|     $searchInput.focus(); | ||||
| } | ||||
| 
 | ||||
| function hideSearch() { | ||||
|     resetSearch(); | ||||
| 
 | ||||
|     $searchResults.hide(); | ||||
|     $searchBox.hide(); | ||||
| } | ||||
| 
 | ||||
| function toggleSearch() { | ||||
|     if ($searchBox.is(":hidden")) { | ||||
|         $searchBox.show(); | ||||
|         $searchInput.focus(); | ||||
|         showSearch(); | ||||
|     } | ||||
|     else { | ||||
|         resetSearch(); | ||||
| 
 | ||||
|         $searchBox.hide(); | ||||
|         hideSearch(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| function resetSearch() { | ||||
|     $searchInput.val(""); | ||||
| 
 | ||||
|     getTree().clearFilter(); | ||||
| } | ||||
| 
 | ||||
| function getTree() { | ||||
|     return $tree.fancytree('getTree'); | ||||
| } | ||||
| 
 | ||||
| async function doSearch() { | ||||
|     const searchText = $searchInput.val(); | ||||
| 
 | ||||
|     const noteIds = await server.get('search/' + encodeURIComponent(searchText)); | ||||
| 
 | ||||
|     for (const noteId of noteIds) { | ||||
|         await treeService.expandToNote(noteId, {noAnimation: true, noEvents: true}); | ||||
| async function doSearch(searchText) { | ||||
|     if (searchText) { | ||||
|         $searchInput.val(searchText); | ||||
|     } | ||||
|     else { | ||||
|         searchText = $searchInput.val(); | ||||
|     } | ||||
| 
 | ||||
|     // Pass a string to perform case insensitive matching
 | ||||
|     getTree().filterBranches(node => noteIds.includes(node.data.noteId)); | ||||
|     const results = await server.get('search/' + encodeURIComponent(searchText)); | ||||
| 
 | ||||
|     $searchResultsInner.empty(); | ||||
|     $searchResults.show(); | ||||
| 
 | ||||
|     for (const result of results) { | ||||
|         const link = $('<a>', { | ||||
|             href: 'javascript:', | ||||
|             text: result.title | ||||
|         }).attr('action', 'note').attr('note-path', result.path); | ||||
| 
 | ||||
|         const $result = $('<li>').append(link); | ||||
| 
 | ||||
|         $searchResultsInner.append($result); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| async function saveSearch() { | ||||
| @@ -71,6 +94,11 @@ $resetSearchButton.click(resetSearch); | ||||
| 
 | ||||
| $saveSearchButton.click(saveSearch); | ||||
| 
 | ||||
| $closeSearchButton.click(hideSearch); | ||||
| 
 | ||||
| export default { | ||||
|     toggleSearch | ||||
|     toggleSearch, | ||||
|     resetSearch, | ||||
|     showSearch, | ||||
|     doSearch | ||||
| }; | ||||
| @@ -5,7 +5,7 @@ import infoService from "./info.js"; | ||||
| function getHeaders() { | ||||
|     let protectedSessionId = null; | ||||
|  | ||||
|     try { // this is because protected session might not be declared in some cases - like when it's included in migration page | ||||
|     try { // this is because protected session might not be declared in some cases | ||||
|         protectedSessionId = protectedSessionHolder.getProtectedSessionId(); | ||||
|     } | ||||
|     catch(e) {} | ||||
| @@ -85,19 +85,17 @@ async function ajax(url, method, data) { | ||||
|     }); | ||||
| } | ||||
|  | ||||
| setTimeout(() => { | ||||
|     if (utils.isElectron()) { | ||||
|         const ipc = require('electron').ipcRenderer; | ||||
| if (utils.isElectron()) { | ||||
|     const ipc = require('electron').ipcRenderer; | ||||
|  | ||||
|         ipc.on('server-response', (event, arg) => { | ||||
|             console.log(utils.now(), "Response #" + arg.requestId + ": " + arg.statusCode); | ||||
|     ipc.on('server-response', (event, arg) => { | ||||
|         console.log(utils.now(), "Response #" + arg.requestId + ": " + arg.statusCode); | ||||
|  | ||||
|             reqResolves[arg.requestId](arg.body); | ||||
|         reqResolves[arg.requestId](arg.body); | ||||
|  | ||||
|             delete reqResolves[arg.requestId]; | ||||
|         }); | ||||
|     } | ||||
| }, 100); | ||||
|         delete reqResolves[arg.requestId]; | ||||
|     }); | ||||
| } | ||||
|  | ||||
| export default { | ||||
|     get, | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import utils from './utils.js'; | ||||
| import server from './server.js'; | ||||
| import infoService from "./info.js"; | ||||
|  | ||||
| async function syncNow() { | ||||
| @@ -19,7 +19,7 @@ async function syncNow() { | ||||
| $("#sync-now-button").click(syncNow); | ||||
|  | ||||
| async function forceNoteSync(noteId) { | ||||
|     const result = await server.post('sync/force-note-sync/' + noteId); | ||||
|     await server.post('sync/force-note-sync/' + noteId); | ||||
|  | ||||
|     infoService.showMessage("Note added to sync queue."); | ||||
| } | ||||
|   | ||||
| @@ -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 { | ||||
| @@ -174,40 +183,29 @@ async function getRunPath(notePath) { | ||||
|     return effectivePath.reverse(); | ||||
| } | ||||
|  | ||||
| async function showParentList(noteId, node) { | ||||
| async function showPaths(noteId, node) { | ||||
|     utils.assertArguments(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.empty(); | ||||
|  | ||||
|         for (const parentNote of parents) { | ||||
|             const parentNotePath = await getSomeNotePath(parentNote); | ||||
|             // this is to avoid having root notes leading '/' | ||||
|             const notePath = parentNotePath ? (parentNotePath + '/' + noteId) : noteId; | ||||
|             const title = await treeUtils.getNotePathTitle(notePath); | ||||
|     for (const parentNote of parents) { | ||||
|         const parentNotePath = await getSomeNotePath(parentNote); | ||||
|         // this is to avoid having root notes leading '/' | ||||
|         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 = linkService.createNoteLink(notePath, title); | ||||
|             } | ||||
|  | ||||
|             $parentListList.append($("<li/>").append(item)); | ||||
|         if (node.getParent().data.noteId === parentNote.noteId) { | ||||
|             item.addClass("current"); | ||||
|         } | ||||
|  | ||||
|         $notePathList.append(item); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -285,15 +283,16 @@ async function treeInitialized() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| function initFancyTree(branch) { | ||||
|     utils.assertArguments(branch); | ||||
| function initFancyTree(tree) { | ||||
|     utils.assertArguments(tree); | ||||
|  | ||||
|     $tree.fancytree({ | ||||
|         autoScroll: true, | ||||
|         keyboard: false, // we takover keyboard handling in the hotkeys plugin | ||||
|         extensions: ["hotkeys", "filter", "dnd", "clones"], | ||||
|         source: branch, | ||||
|         scrollParent: $("#tree"), | ||||
|         source: tree, | ||||
|         scrollParent: $tree, | ||||
|         minExpandLevel: 2, // root can't be collapsed | ||||
|         click: (event, data) => { | ||||
|             const targetType = data.targetType; | ||||
|             const node = data.node; | ||||
| @@ -319,7 +318,7 @@ function initFancyTree(branch) { | ||||
|  | ||||
|             noteDetailService.switchToNote(node.noteId); | ||||
|  | ||||
|             showParentList(node.noteId, data.node); | ||||
|             showPaths(node.noteId, data.node); | ||||
|         }, | ||||
|         expand: (event, data) => setExpandedToServer(data.node.data.branchId, true), | ||||
|         collapse: (event, data) => setExpandedToServer(data.node.data.branchId, false), | ||||
| @@ -375,7 +374,7 @@ async function loadTree() { | ||||
|         startNotePath = getNotePathFromAddress(); | ||||
|     } | ||||
|  | ||||
|     return await treeBuilder.prepareTree(resp.notes, resp.branches); | ||||
|     return await treeBuilder.prepareTree(resp.notes, resp.branches, resp.relations); | ||||
| } | ||||
|  | ||||
| function collapseTree(node = null) { | ||||
| @@ -541,9 +540,9 @@ $(window).bind('hashchange', function() { | ||||
| }); | ||||
|  | ||||
| utils.bindShortcut('alt+c', () => collapseTree()); // don't use shortened form since collapseTree() accepts argument | ||||
| $collapseTreeButton.click(() => collapseTree()); | ||||
|  | ||||
| $createTopLevelNoteButton.click(createNewTopLevelNote); | ||||
| $collapseTreeButton.click(collapseTree); | ||||
| $scrollToCurrentNoteButton.click(scrollToCurrentNote); | ||||
|  | ||||
| export default { | ||||
|   | ||||
| @@ -5,12 +5,12 @@ import server from "./server.js"; | ||||
| import treeCache from "./tree_cache.js"; | ||||
| import messagingService from "./messaging.js"; | ||||
|  | ||||
| async function prepareTree(noteRows, branchRows) { | ||||
|     utils.assertArguments(noteRows); | ||||
| async function prepareTree(noteRows, branchRows, relations) { | ||||
|     utils.assertArguments(noteRows, branchRows, relations); | ||||
|  | ||||
|     treeCache.load(noteRows, branchRows); | ||||
|     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,32 +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 | ||||
|         }; | ||||
|  | ||||
|         const hasChildren = (await note.getChildNotes()).length > 0; | ||||
|  | ||||
|         if (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); | ||||
|     } | ||||
| @@ -70,14 +74,21 @@ async function prepareRealBranch(parentNote) { | ||||
|  | ||||
| async function prepareSearchBranch(note) { | ||||
|     const fullNote = await noteDetailService.loadNote(note.noteId); | ||||
|     const noteIds = await server.get('search/' + encodeURIComponent(fullNote.jsonContent.searchString)); | ||||
|     const results = await server.get('search/' + encodeURIComponent(fullNote.jsonContent.searchString)); | ||||
|  | ||||
|     const noteIds = results.map(res => res.noteId); | ||||
|  | ||||
|     // force to load all the notes at once instead of one by one | ||||
|     await treeCache.getNotes(noteIds); | ||||
|  | ||||
|     for (const result of results) { | ||||
|         const origBranch = await treeCache.getBranch(result.branchId); | ||||
|  | ||||
|     for (const noteId of noteIds) { | ||||
|         const branch = new Branch(treeCache, { | ||||
|             branchId: "virt" + utils.randomString(10), | ||||
|             noteId: noteId, | ||||
|             noteId: result.noteId, | ||||
|             parentNoteId: note.noteId, | ||||
|             prefix: '', | ||||
|             prefix: origBranch.prefix, | ||||
|             virtual: true | ||||
|         }); | ||||
|  | ||||
| @@ -92,11 +103,15 @@ async function getExtraClasses(note) { | ||||
|  | ||||
|     const extraClasses = []; | ||||
|  | ||||
|     if (note.noteId === 'root') { | ||||
|         extraClasses.push("tree-root"); | ||||
|     } | ||||
|  | ||||
|     if (note.isProtected) { | ||||
|         extraClasses.push("protected"); | ||||
|     } | ||||
|  | ||||
|     if ((await note.getParentNotes()).length > 1) { | ||||
|     if (note.getParentNoteIds().length > 1) { | ||||
|         extraClasses.push("multiple-parents"); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -2,45 +2,93 @@ import utils from "./utils.js"; | ||||
| import Branch from "../entities/branch.js"; | ||||
| import NoteShort from "../entities/note_short.js"; | ||||
| import infoService from "./info.js"; | ||||
| import server from "./server.js"; | ||||
|  | ||||
| class TreeCache { | ||||
|     load(noteRows, branchRows) { | ||||
|         this.parents = []; | ||||
|         this.children = []; | ||||
|     load(noteRows, branchRows, relations) { | ||||
|         this.parents = {}; | ||||
|         this.children = {}; | ||||
|         this.childParentToBranch = {}; | ||||
|  | ||||
|         /** @type {Object.<string, NoteShort>} */ | ||||
|         this.notes = {}; | ||||
|  | ||||
|         /** @type {Object.<string, Branch>} */ | ||||
|         this.branches = {}; | ||||
|  | ||||
|         this.addResp(noteRows, branchRows, relations); | ||||
|     } | ||||
|  | ||||
|     addResp(noteRows, branchRows, relations) { | ||||
|         for (const noteRow of noteRows) { | ||||
|             const note = new NoteShort(this, noteRow); | ||||
|  | ||||
|             this.notes[note.noteId] = note; | ||||
|         } | ||||
|  | ||||
|         /** @type {Object.<string, Branch>} */ | ||||
|         this.branches = {}; | ||||
|         for (const branchRow of branchRows) { | ||||
|             const branch = new Branch(this, branchRow); | ||||
|  | ||||
|             this.addBranch(branch); | ||||
|         } | ||||
|  | ||||
|         for (const relation of relations) { | ||||
|             this.addBranchRelationship(relation.branchId, relation.childNoteId, relation.parentNoteId); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async getNotes(noteIds) { | ||||
|         const missingNoteIds = noteIds.filter(noteId => this.notes[noteId] === undefined); | ||||
|  | ||||
|         if (missingNoteIds.length > 0) { | ||||
|             const resp = await server.post('tree/load', { noteIds: missingNoteIds }); | ||||
|  | ||||
|             this.addResp(resp.notes, resp.branches, resp.relations); | ||||
|         } | ||||
|  | ||||
|         return noteIds.map(noteId => { | ||||
|             if (!this.notes[noteId]) { | ||||
|                 throw new Error(`Can't find note ${noteId}`); | ||||
|             } | ||||
|             else { | ||||
|                 return this.notes[noteId]; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /** @return NoteShort */ | ||||
|     async getNote(noteId) { | ||||
|         return this.notes[noteId]; | ||||
|         if (noteId === 'none') { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         return (await this.getNotes([noteId]))[0]; | ||||
|     } | ||||
|  | ||||
|     addBranch(branch) { | ||||
|         this.branches[branch.branchId] = branch; | ||||
|  | ||||
|         this.parents[branch.noteId] = this.parents[branch.noteId] || []; | ||||
|         this.parents[branch.noteId].push(this.notes[branch.parentNoteId]); | ||||
|         this.addBranchRelationship(branch.branchId, branch.noteId, branch.parentNoteId); | ||||
|     } | ||||
|  | ||||
|         this.children[branch.parentNoteId] = this.children[branch.parentNoteId] || []; | ||||
|         this.children[branch.parentNoteId].push(this.notes[branch.noteId]); | ||||
|     addBranchRelationship(branchId, childNoteId, parentNoteId) { | ||||
|         if (parentNoteId === 'none') { // applies only to root element | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.childParentToBranch[branch.noteId + '-' + branch.parentNoteId] = branch; | ||||
|         this.childParentToBranch[childNoteId + '-' + parentNoteId] = branchId; | ||||
|  | ||||
|         this.parents[childNoteId] = this.parents[childNoteId] || []; | ||||
|  | ||||
|         if (!this.parents[childNoteId].includes(parentNoteId)) { | ||||
|             this.parents[childNoteId].push(parentNoteId); | ||||
|         } | ||||
|  | ||||
|         this.children[parentNoteId] = this.children[parentNoteId] || []; | ||||
|  | ||||
|         if (!this.children[parentNoteId].includes(childNoteId)) { | ||||
|             this.children[parentNoteId].push(childNoteId); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     add(note, branch) { | ||||
| @@ -49,21 +97,46 @@ class TreeCache { | ||||
|         this.addBranch(branch); | ||||
|     } | ||||
|  | ||||
|     async getBranches(branchIds) { | ||||
|         const missingBranchIds = branchIds.filter(branchId => this.branches[branchId] === undefined); | ||||
|  | ||||
|         if (missingBranchIds.length > 0) { | ||||
|             const resp = await server.post('tree/load', { branchIds: branchIds }); | ||||
|  | ||||
|             this.addResp(resp.notes, resp.branches, resp.relations); | ||||
|         } | ||||
|  | ||||
|         return branchIds.map(branchId => { | ||||
|             if (!this.branches[branchId]) { | ||||
|                 throw new Error(`Can't find branch ${branchId}`); | ||||
|             } | ||||
|             else { | ||||
|                 return this.branches[branchId]; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /** @return Branch */ | ||||
|     async getBranch(branchId) { | ||||
|         return this.branches[branchId]; | ||||
|         return (await this.getBranches([branchId]))[0]; | ||||
|     } | ||||
|  | ||||
|     /** @return Branch */ | ||||
|     async getBranchByChildParent(childNoteId, parentNoteId) { | ||||
|         const key = (childNoteId + '-' + parentNoteId); | ||||
|         const branch = this.childParentToBranch[key]; | ||||
|         const branchId = this.getBranchIdByChildParent(childNoteId, parentNoteId); | ||||
|  | ||||
|         if (!branch) { | ||||
|         return await this.getBranch(branchId); | ||||
|     } | ||||
|  | ||||
|     getBranchIdByChildParent(childNoteId, parentNoteId) { | ||||
|         const key = childNoteId + '-' + parentNoteId; | ||||
|         const branchId = this.childParentToBranch[key]; | ||||
|  | ||||
|         if (!branchId) { | ||||
|             infoService.throwError("Cannot find branch for child-parent=" + key); | ||||
|         } | ||||
|  | ||||
|         return branch; | ||||
|         return branchId; | ||||
|     } | ||||
|  | ||||
|     /* Move note from one parent to another. */ | ||||
| @@ -78,33 +151,14 @@ class TreeCache { | ||||
|         delete treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId]; // this is correct because we know that oldParentId isn't same as newParentId | ||||
|  | ||||
|         // remove old associations | ||||
|         treeCache.parents[childNoteId] = treeCache.parents[childNoteId].filter(p => p.noteId !== oldParentNoteId); | ||||
|         treeCache.children[oldParentNoteId] = treeCache.children[oldParentNoteId].filter(ch => ch.noteId !== childNoteId); | ||||
|         treeCache.parents[childNoteId] = treeCache.parents[childNoteId].filter(p => p !== oldParentNoteId); | ||||
|         treeCache.children[oldParentNoteId] = treeCache.children[oldParentNoteId].filter(ch => ch !== childNoteId); | ||||
|  | ||||
|         // add new associations | ||||
|         treeCache.parents[childNoteId].push(await treeCache.getNote(newParentNoteId)); | ||||
|         treeCache.parents[childNoteId].push(newParentNoteId); | ||||
|  | ||||
|         treeCache.children[newParentNoteId] = treeCache.children[newParentNoteId] || []; // this might be first child | ||||
|         treeCache.children[newParentNoteId].push(await treeCache.getNote(childNoteId)); | ||||
|     } | ||||
|  | ||||
|     removeParentChildRelation(parentNoteId, childNoteId) { | ||||
|         utils.assertArguments(parentNoteId, childNoteId); | ||||
|  | ||||
|         treeCache.parents[childNoteId] = treeCache.parents[childNoteId].filter(p => p.noteId !== parentNoteId); | ||||
|         treeCache.children[parentNoteId] = treeCache.children[parentNoteId].filter(ch => ch.noteId !== childNoteId); | ||||
|  | ||||
|         delete treeCache.childParentToBranch[childNoteId + '-' + parentNoteId]; | ||||
|     } | ||||
|  | ||||
|     async setParentChildRelation(branchId, parentNoteId, childNoteId) { | ||||
|         treeCache.parents[childNoteId] = treeCache.parents[childNoteId] || []; | ||||
|         treeCache.parents[childNoteId].push(await treeCache.getNote(parentNoteId)); | ||||
|  | ||||
|         treeCache.children[parentNoteId] = treeCache.children[parentNoteId] || []; | ||||
|         treeCache.children[parentNoteId].push(await treeCache.getNote(childNoteId)); | ||||
|  | ||||
|         treeCache.childParentToBranch[childNoteId + '-' + parentNoteId] = await treeCache.getBranch(branchId); | ||||
|         treeCache.children[newParentNoteId].push(childNoteId); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -52,6 +52,15 @@ async function getNotePathTitle(notePath) { | ||||
|  | ||||
|     const titlePath = []; | ||||
|  | ||||
|     if (notePath.startsWith('root/')) { | ||||
|         notePath = notePath.substr(5); | ||||
|     } | ||||
|  | ||||
|     // special case when we want just root's title | ||||
|     if (notePath === 'root') { | ||||
|         return await getNoteTitle(notePath); | ||||
|     } | ||||
|  | ||||
|     let parentNoteId = 'root'; | ||||
|  | ||||
|     for (const noteId of notePath.split('/')) { | ||||
|   | ||||
							
								
								
									
										51
									
								
								src/public/javascripts/services/zoom.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,51 @@ | ||||
| import server from "./server.js"; | ||||
| import utils from "./utils.js"; | ||||
| import optionsInitService from "./options_init.js"; | ||||
|  | ||||
| const MIN_ZOOM = 0.5; | ||||
| const MAX_ZOOM = 2.0; | ||||
|  | ||||
| async function decreaseZoomFactor() { | ||||
|     await setZoomFactorAndSave(getCurrentZoom() - 0.1); | ||||
| } | ||||
|  | ||||
| async function increaseZoomFactor() { | ||||
|     await setZoomFactorAndSave(getCurrentZoom() + 0.1); | ||||
| } | ||||
|  | ||||
| function setZoomFactor(zoomFactor) { | ||||
|     zoomFactor = parseFloat(zoomFactor); | ||||
|  | ||||
|     const webFrame = require('electron').webFrame; | ||||
|     webFrame.setZoomFactor(zoomFactor); | ||||
| } | ||||
|  | ||||
| async function setZoomFactorAndSave(zoomFactor) { | ||||
|     if (!utils.isElectron()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (zoomFactor >= MIN_ZOOM && zoomFactor <= MAX_ZOOM) { | ||||
|         setZoomFactor(zoomFactor); | ||||
|  | ||||
|         await server.put('options/zoomFactor/' + zoomFactor); | ||||
|     } | ||||
|     else { | ||||
|         console.log(`Zoom factor ${zoomFactor} outside of the range, ignored.`); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function getCurrentZoom() { | ||||
|     return require('electron').webFrame.getZoomFactor(); | ||||
| } | ||||
|  | ||||
| if (utils.isElectron()) { | ||||
|     optionsInitService.optionsReady.then(options => setZoomFactor(options.zoomFactor)) | ||||
| } | ||||
|  | ||||
| export default { | ||||
|     decreaseZoomFactor, | ||||
|     increaseZoomFactor, | ||||
|     setZoomFactor, | ||||
|     setZoomFactorAndSave | ||||
| } | ||||