Compare commits
	
		
			158 Commits
		
	
	
		
			v0.10.2-be
			...
			v0.18.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 3d185a5178 | ||
|  | 2ff7a890bc | ||
|  | 2eb1a9705f | ||
|  | ed1381103a | ||
|  | 170d317589 | ||
|  | ededc063df | ||
|  | 986eace1be | ||
|  | 29086d8dfe | ||
|  | 9b3f3fde05 | ||
|  | 6a50afd952 | ||
|  | 697eee2706 | ||
|  | 8a95afd756 | ||
|  | 4d6eda8fe6 | ||
|  | e4f459fa2b | ||
|  | f578e001b0 | ||
|  | 2a08aef885 | ||
|  | 7564bf388c | ||
|  | 7e4d70259f | ||
|  | 5b98c1c0f3 | ||
|  | 02dc7b199b | ||
|  | d39cdbfada | ||
|  | 50bb4a47ee | ||
|  | a4627f2ddb | ||
|  | c8253caae9 | ||
|  | 0ece9bd1be | ||
|  | b6935abcc9 | ||
|  | 37ab7b4641 | ||
|  | 013714cb5c | ||
|  | 1fe7c62f5a | ||
|  | a06618d851 | ||
|  | e7460ca3a9 | ||
|  | 073300bbcd | ||
|  | a201661ce5 | ||
|  | 6235a3c886 | ||
|  | 3972c27e7a | ||
|  | 14cffbbe62 | ||
|  | 599c3c04af | ||
|  | f1412b631d | ||
|  | 41908050bb | ||
|  | f07033423c | ||
|  | daf96fcbf2 | ||
|  | 2bca94529e | ||
|  | b2c9a0da21 | ||
|  | 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 | 
							
								
								
									
										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"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <dataSource name="document.db"> | <dataSource name="document.db"> | ||||||
|   <database-model serializer="dbm" rdbms="SQLITE" format-version="4.7"> |   <database-model serializer="dbm" rdbms="SQLITE" format-version="4.9"> | ||||||
|     <root id="1"/> |     <root id="1"> | ||||||
|  |       <ServerVersion>3.16.1</ServerVersion> | ||||||
|  |     </root> | ||||||
|     <schema id="2" parent="1" name="main"> |     <schema id="2" parent="1" name="main"> | ||||||
|       <Current>1</Current> |       <Current>1</Current> | ||||||
|       <Visible>1</Visible> |       <Visible>1</Visible> | ||||||
| @@ -19,568 +21,718 @@ | |||||||
|     <table id="13" parent="2" name="notes"/> |     <table id="13" parent="2" name="notes"/> | ||||||
|     <table id="14" parent="2" name="options"/> |     <table id="14" parent="2" name="options"/> | ||||||
|     <table id="15" parent="2" name="recent_notes"/> |     <table id="15" parent="2" name="recent_notes"/> | ||||||
|     <table id="16" parent="2" name="source_ids"/> |     <table id="16" parent="2" name="relations"/> | ||||||
|     <table id="17" parent="2" name="sqlite_master"> |     <table id="17" parent="2" name="source_ids"/> | ||||||
|  |     <table id="18" parent="2" name="sqlite_master"> | ||||||
|       <System>1</System> |       <System>1</System> | ||||||
|     </table> |     </table> | ||||||
|     <table id="18" parent="2" name="sqlite_sequence"> |     <table id="19" parent="2" name="sqlite_sequence"> | ||||||
|       <System>1</System> |       <System>1</System> | ||||||
|     </table> |     </table> | ||||||
|     <table id="19" parent="2" name="sync"/> |     <table id="20" parent="2" name="sync"/> | ||||||
|     <column id="20" parent="6" name="apiTokenId"> |     <column id="21" parent="6" name="apiTokenId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="21" parent="6" name="token"> |     <column id="22" parent="6" name="token"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="22" parent="6" name="dateCreated"> |     <column id="23" parent="6" name="dateCreated"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="23" parent="6" name="isDeleted"> |     <column id="24" parent="6" name="isDeleted"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <index id="24" parent="6" name="sqlite_autoindex_api_tokens_1"> |     <column id="25" parent="6" name="hash"> | ||||||
|  |       <Position>5</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>""</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <index id="26" parent="6" name="sqlite_autoindex_api_tokens_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>apiTokenId</ColNames> |       <ColNames>apiTokenId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <key id="25" parent="6"> |     <key id="27" parent="6"> | ||||||
|       <ColNames>apiTokenId</ColNames> |       <ColNames>apiTokenId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_api_tokens_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_api_tokens_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="26" parent="7" name="branchId"> |     <column id="28" parent="7" name="branchId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="27" parent="7" name="noteId"> |     <column id="29" parent="7" name="noteId"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="28" parent="7" name="parentNoteId"> |     <column id="30" parent="7" name="parentNoteId"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="29" parent="7" name="notePosition"> |     <column id="31" parent="7" name="notePosition"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>INTEGER|0s</DataType> |       <DataType>INTEGER|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="30" parent="7" name="prefix"> |     <column id="32" parent="7" name="prefix"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="31" parent="7" name="isExpanded"> |     <column id="33" parent="7" name="isExpanded"> | ||||||
|       <Position>6</Position> |       <Position>6</Position> | ||||||
|       <DataType>BOOLEAN|0s</DataType> |       <DataType>BOOLEAN|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="32" parent="7" name="isDeleted"> |     <column id="34" parent="7" name="isDeleted"> | ||||||
|       <Position>7</Position> |       <Position>7</Position> | ||||||
|       <DataType>INTEGER|0s</DataType> |       <DataType>INTEGER|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="33" parent="7" name="dateModified"> |     <column id="35" parent="7" name="dateModified"> | ||||||
|       <Position>8</Position> |       <Position>8</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <index id="34" parent="7" name="sqlite_autoindex_branches_1"> |     <column id="36" parent="7" name="hash"> | ||||||
|  |       <Position>9</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>""</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <column id="37" 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="38" parent="7" name="sqlite_autoindex_branches_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>branchId</ColNames> |       <ColNames>branchId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <index id="35" parent="7" name="IDX_branches_noteId_parentNoteId"> |     <index id="39" parent="7" name="IDX_branches_noteId_parentNoteId"> | ||||||
|       <ColNames>noteId |       <ColNames>noteId | ||||||
| parentNoteId</ColNames> | parentNoteId</ColNames> | ||||||
|       <ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
| </ColumnCollations> |  | ||||||
|     </index> |     </index> | ||||||
|     <index id="36" parent="7" name="IDX_branches_noteId"> |     <index id="40" parent="7" name="IDX_branches_noteId"> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <key id="37" parent="7"> |     <index id="41" parent="7" name="IDX_branches_parentNoteId"> | ||||||
|  |       <ColNames>parentNoteId</ColNames> | ||||||
|  |       <ColumnCollations></ColumnCollations> | ||||||
|  |     </index> | ||||||
|  |     <key id="42" parent="7"> | ||||||
|       <ColNames>branchId</ColNames> |       <ColNames>branchId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="38" parent="8" name="id"> |     <column id="43" parent="8" name="eventId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>INTEGER|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <SequenceIdentity>1</SequenceIdentity> |  | ||||||
|     </column> |     </column> | ||||||
|     <column id="39" parent="8" name="noteId"> |     <column id="44" parent="8" name="noteId"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="40" parent="8" name="comment"> |     <column id="45" parent="8" name="comment"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="41" parent="8" name="dateAdded"> |     <column id="46" parent="8" name="dateCreated"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <key id="42" parent="8"> |     <index id="47" parent="8" name="sqlite_autoindex_event_log_1"> | ||||||
|       <ColNames>id</ColNames> |       <NameSurrogate>1</NameSurrogate> | ||||||
|  |       <ColNames>eventId</ColNames> | ||||||
|  |       <ColumnCollations></ColumnCollations> | ||||||
|  |       <Unique>1</Unique> | ||||||
|  |     </index> | ||||||
|  |     <key id="48" parent="8"> | ||||||
|  |       <ColNames>eventId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|  |       <UnderlyingIndexName>sqlite_autoindex_event_log_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <foreign-key id="43" parent="8"> |     <column id="49" parent="9" name="imageId"> | ||||||
|       <ColNames>noteId</ColNames> |  | ||||||
|       <RefTableName>notes</RefTableName> |  | ||||||
|       <RefColNames>noteId</RefColNames> |  | ||||||
|     </foreign-key> |  | ||||||
|     <column id="44" parent="9" name="imageId"> |  | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="45" parent="9" name="format"> |     <column id="50" parent="9" name="format"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="46" parent="9" name="checksum"> |     <column id="51" parent="9" name="checksum"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="47" parent="9" name="name"> |     <column id="52" parent="9" name="name"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="48" parent="9" name="data"> |     <column id="53" parent="9" name="data"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>BLOB|0s</DataType> |       <DataType>BLOB|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="49" parent="9" name="isDeleted"> |     <column id="54" parent="9" name="isDeleted"> | ||||||
|       <Position>6</Position> |       <Position>6</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="50" parent="9" name="dateModified"> |     <column id="55" parent="9" name="dateModified"> | ||||||
|       <Position>7</Position> |       <Position>7</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="51" parent="9" name="dateCreated"> |     <column id="56" parent="9" name="dateCreated"> | ||||||
|       <Position>8</Position> |       <Position>8</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <index id="52" parent="9" name="sqlite_autoindex_images_1"> |     <column id="57" parent="9" name="hash"> | ||||||
|  |       <Position>9</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>""</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <index id="58" parent="9" name="sqlite_autoindex_images_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>imageId</ColNames> |       <ColNames>imageId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <key id="53" parent="9"> |     <key id="59" parent="9"> | ||||||
|       <ColNames>imageId</ColNames> |       <ColNames>imageId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="54" parent="10" name="labelId"> |     <column id="60" parent="10" name="labelId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="55" parent="10" name="noteId"> |     <column id="61" parent="10" name="noteId"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="56" parent="10" name="name"> |     <column id="62" parent="10" name="name"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="57" parent="10" name="value"> |     <column id="63" parent="10" name="value"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>''</DefaultExpression> |       <DefaultExpression>''</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="58" parent="10" name="position"> |     <column id="64" parent="10" name="position"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="59" parent="10" name="dateCreated"> |     <column id="65" parent="10" name="dateCreated"> | ||||||
|       <Position>6</Position> |       <Position>6</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="60" parent="10" name="dateModified"> |     <column id="66" parent="10" name="dateModified"> | ||||||
|       <Position>7</Position> |       <Position>7</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="61" parent="10" name="isDeleted"> |     <column id="67" parent="10" name="isDeleted"> | ||||||
|       <Position>8</Position> |       <Position>8</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <index id="62" parent="10" name="sqlite_autoindex_labels_1"> |     <column id="68" parent="10" name="hash"> | ||||||
|  |       <Position>9</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>""</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <index id="69" parent="10" name="sqlite_autoindex_labels_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>labelId</ColNames> |       <ColNames>labelId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <index id="63" parent="10" name="IDX_labels_noteId"> |     <index id="70" parent="10" name="IDX_labels_noteId"> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <index id="64" parent="10" name="IDX_labels_name_value"> |     <index id="71" parent="10" name="IDX_labels_name_value"> | ||||||
|       <ColNames>name |       <ColNames>name | ||||||
| value</ColNames> | value</ColNames> | ||||||
|       <ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
| </ColumnCollations> |  | ||||||
|     </index> |     </index> | ||||||
|     <key id="65" parent="10"> |     <key id="72" parent="10"> | ||||||
|       <ColNames>labelId</ColNames> |       <ColNames>labelId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="66" parent="11" name="noteImageId"> |     <column id="73" parent="11" name="noteImageId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="67" parent="11" name="noteId"> |     <column id="74" parent="11" name="noteId"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="68" parent="11" name="imageId"> |     <column id="75" parent="11" name="imageId"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="69" parent="11" name="isDeleted"> |     <column id="76" parent="11" name="isDeleted"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="70" parent="11" name="dateModified"> |     <column id="77" parent="11" name="dateModified"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="71" parent="11" name="dateCreated"> |     <column id="78" parent="11" name="dateCreated"> | ||||||
|       <Position>6</Position> |       <Position>6</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <index id="72" parent="11" name="sqlite_autoindex_note_images_1"> |     <column id="79" parent="11" name="hash"> | ||||||
|  |       <Position>7</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>""</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <index id="80" parent="11" name="sqlite_autoindex_note_images_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>noteImageId</ColNames> |       <ColNames>noteImageId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <index id="73" parent="11" name="IDX_note_images_noteId_imageId"> |     <index id="81" parent="11" name="IDX_note_images_noteId_imageId"> | ||||||
|       <ColNames>noteId |       <ColNames>noteId | ||||||
| imageId</ColNames> | imageId</ColNames> | ||||||
|       <ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
| </ColumnCollations> |  | ||||||
|     </index> |     </index> | ||||||
|     <index id="74" parent="11" name="IDX_note_images_noteId"> |     <index id="82" parent="11" name="IDX_note_images_noteId"> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <index id="75" parent="11" name="IDX_note_images_imageId"> |     <index id="83" parent="11" name="IDX_note_images_imageId"> | ||||||
|       <ColNames>imageId</ColNames> |       <ColNames>imageId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <key id="76" parent="11"> |     <key id="84" parent="11"> | ||||||
|       <ColNames>noteImageId</ColNames> |       <ColNames>noteImageId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_note_images_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_note_images_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="77" parent="12" name="noteRevisionId"> |     <column id="85" parent="12" name="noteRevisionId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="78" parent="12" name="noteId"> |     <column id="86" parent="12" name="noteId"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="79" parent="12" name="title"> |     <column id="87" parent="12" name="title"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="80" parent="12" name="content"> |     <column id="88" parent="12" name="content"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="81" parent="12" name="isProtected"> |     <column id="89" parent="12" name="isProtected"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="82" parent="12" name="dateModifiedFrom"> |     <column id="90" parent="12" name="dateModifiedFrom"> | ||||||
|       <Position>6</Position> |       <Position>6</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="83" parent="12" name="dateModifiedTo"> |     <column id="91" parent="12" name="dateModifiedTo"> | ||||||
|       <Position>7</Position> |       <Position>7</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <index id="84" parent="12" name="sqlite_autoindex_note_revisions_1"> |     <column id="92" parent="12" name="type"> | ||||||
|  |       <Position>8</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>''</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <column id="93" parent="12" name="mime"> | ||||||
|  |       <Position>9</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>''</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <column id="94" parent="12" name="hash"> | ||||||
|  |       <Position>10</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>""</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <index id="95" parent="12" name="sqlite_autoindex_note_revisions_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>noteRevisionId</ColNames> |       <ColNames>noteRevisionId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <index id="85" parent="12" name="IDX_note_revisions_noteId"> |     <index id="96" parent="12" name="IDX_note_revisions_noteId"> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <index id="86" parent="12" name="IDX_note_revisions_dateModifiedFrom"> |     <index id="97" parent="12" name="IDX_note_revisions_dateModifiedFrom"> | ||||||
|       <ColNames>dateModifiedFrom</ColNames> |       <ColNames>dateModifiedFrom</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <index id="87" parent="12" name="IDX_note_revisions_dateModifiedTo"> |     <index id="98" parent="12" name="IDX_note_revisions_dateModifiedTo"> | ||||||
|       <ColNames>dateModifiedTo</ColNames> |       <ColNames>dateModifiedTo</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <key id="88" parent="12"> |     <key id="99" parent="12"> | ||||||
|       <ColNames>noteRevisionId</ColNames> |       <ColNames>noteRevisionId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="89" parent="13" name="noteId"> |     <column id="100" parent="13" name="noteId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="90" parent="13" name="title"> |     <column id="101" parent="13" name="title"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>"unnamed"</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="91" parent="13" name="content"> |     <column id="102" parent="13" name="content"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>""</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="92" parent="13" name="isProtected"> |     <column id="103" parent="13" name="isProtected"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="93" parent="13" name="isDeleted"> |     <column id="104" parent="13" name="isDeleted"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="94" parent="13" name="dateCreated"> |     <column id="105" parent="13" name="dateCreated"> | ||||||
|       <Position>6</Position> |       <Position>6</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="95" parent="13" name="dateModified"> |     <column id="106" parent="13" name="dateModified"> | ||||||
|       <Position>7</Position> |       <Position>7</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="96" parent="13" name="type"> |     <column id="107" parent="13" name="type"> | ||||||
|       <Position>8</Position> |       <Position>8</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>'text'</DefaultExpression> |       <DefaultExpression>'text'</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="97" parent="13" name="mime"> |     <column id="108" parent="13" name="mime"> | ||||||
|       <Position>9</Position> |       <Position>9</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>'text/html'</DefaultExpression> |       <DefaultExpression>'text/html'</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <index id="98" parent="13" name="sqlite_autoindex_notes_1"> |     <column id="109" parent="13" name="hash"> | ||||||
|  |       <Position>10</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>""</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <index id="110" parent="13" name="sqlite_autoindex_notes_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <index id="99" parent="13" name="IDX_notes_isDeleted"> |     <index id="111" parent="13" name="IDX_notes_type"> | ||||||
|       <ColNames>isDeleted</ColNames> |       <ColNames>type</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <key id="100" parent="13"> |     <key id="112" parent="13"> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="101" parent="14" name="name"> |     <column id="113" parent="14" name="name"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="102" parent="14" name="value"> |     <column id="114" parent="14" name="value"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="103" parent="14" name="dateModified"> |     <column id="115" parent="14" name="dateModified"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="104" parent="14" name="isSynced"> |     <column id="116" parent="14" name="isSynced"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>INTEGER|0s</DataType> |       <DataType>INTEGER|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <index id="105" parent="14" name="sqlite_autoindex_options_1"> |     <column id="117" parent="14" name="hash"> | ||||||
|  |       <Position>5</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>""</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <column id="118" parent="14" name="dateCreated"> | ||||||
|  |       <Position>6</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>'1970-01-01T00:00:00.000Z'</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <index id="119" parent="14" name="sqlite_autoindex_options_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>name</ColNames> |       <ColNames>name</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <key id="106" parent="14"> |     <key id="120" parent="14"> | ||||||
|       <ColNames>name</ColNames> |       <ColNames>name</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="107" parent="15" name="branchId"> |     <column id="121" parent="15" name="branchId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="108" parent="15" name="notePath"> |     <column id="122" parent="15" name="notePath"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="109" parent="15" name="dateAccessed"> |     <column id="123" parent="15" name="hash"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>""</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="110" parent="15" name="isDeleted"> |     <column id="124" parent="15" name="dateCreated"> | ||||||
|       <Position>4</Position> |       <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> |       <DataType>INT|0s</DataType> | ||||||
|     </column> |     </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> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>branchId</ColNames> |       <ColNames>branchId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <key id="112" parent="15"> |     <key id="127" parent="15"> | ||||||
|       <ColNames>branchId</ColNames> |       <ColNames>branchId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="113" parent="16" name="sourceId"> |     <column id="128" parent="16" name="relationId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="114" parent="16" name="dateCreated"> |     <column id="129" parent="16" name="sourceNoteId"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <index id="115" parent="16" name="sqlite_autoindex_source_ids_1"> |     <column id="130" parent="16" name="name"> | ||||||
|  |       <Position>3</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |     </column> | ||||||
|  |     <column id="131" parent="16" name="targetNoteId"> | ||||||
|  |       <Position>4</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |     </column> | ||||||
|  |     <column id="132" parent="16" name="position"> | ||||||
|  |       <Position>5</Position> | ||||||
|  |       <DataType>INT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>0</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <column id="133" parent="16" name="dateCreated"> | ||||||
|  |       <Position>6</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |     </column> | ||||||
|  |     <column id="134" parent="16" name="dateModified"> | ||||||
|  |       <Position>7</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |     </column> | ||||||
|  |     <column id="135" parent="16" name="isDeleted"> | ||||||
|  |       <Position>8</Position> | ||||||
|  |       <DataType>INT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |     </column> | ||||||
|  |     <column id="136" parent="16" name="hash"> | ||||||
|  |       <Position>9</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>""</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <index id="137" parent="16" name="sqlite_autoindex_relations_1"> | ||||||
|  |       <NameSurrogate>1</NameSurrogate> | ||||||
|  |       <ColNames>relationId</ColNames> | ||||||
|  |       <ColumnCollations></ColumnCollations> | ||||||
|  |       <Unique>1</Unique> | ||||||
|  |     </index> | ||||||
|  |     <index id="138" parent="16" name="IDX_relation_sourceNoteId"> | ||||||
|  |       <ColNames>sourceNoteId</ColNames> | ||||||
|  |       <ColumnCollations></ColumnCollations> | ||||||
|  |     </index> | ||||||
|  |     <index id="139" parent="16" name="IDX_relation_targetNoteId"> | ||||||
|  |       <ColNames>targetNoteId</ColNames> | ||||||
|  |       <ColumnCollations></ColumnCollations> | ||||||
|  |     </index> | ||||||
|  |     <key id="140" parent="16"> | ||||||
|  |       <ColNames>relationId</ColNames> | ||||||
|  |       <Primary>1</Primary> | ||||||
|  |       <UnderlyingIndexName>sqlite_autoindex_relations_1</UnderlyingIndexName> | ||||||
|  |     </key> | ||||||
|  |     <column id="141" parent="17" name="sourceId"> | ||||||
|  |       <Position>1</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |     </column> | ||||||
|  |     <column id="142" parent="17" name="dateCreated"> | ||||||
|  |       <Position>2</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |     </column> | ||||||
|  |     <index id="143" parent="17" name="sqlite_autoindex_source_ids_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>sourceId</ColNames> |       <ColNames>sourceId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <key id="116" parent="16"> |     <key id="144" parent="17"> | ||||||
|       <ColNames>sourceId</ColNames> |       <ColNames>sourceId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="117" parent="17" name="type"> |     <column id="145" parent="18" name="type"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>text|0s</DataType> |       <DataType>text|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="118" parent="17" name="name"> |     <column id="146" parent="18" name="name"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>text|0s</DataType> |       <DataType>text|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="119" parent="17" name="tbl_name"> |     <column id="147" parent="18" name="tbl_name"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>text|0s</DataType> |       <DataType>text|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="120" parent="17" name="rootpage"> |     <column id="148" parent="18" name="rootpage"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>integer|0s</DataType> |       <DataType>integer|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="121" parent="17" name="sql"> |     <column id="149" parent="18" name="sql"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>text|0s</DataType> |       <DataType>text|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="122" parent="18" name="name"> |     <column id="150" parent="19" name="name"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|     </column> |     </column> | ||||||
|     <column id="123" parent="18" name="seq"> |     <column id="151" parent="19" name="seq"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|     </column> |     </column> | ||||||
|     <column id="124" parent="19" name="id"> |     <column id="152" parent="20" name="id"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>INTEGER|0s</DataType> |       <DataType>INTEGER|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <SequenceIdentity>1</SequenceIdentity> |       <SequenceIdentity>1</SequenceIdentity> | ||||||
|     </column> |     </column> | ||||||
|     <column id="125" parent="19" name="entityName"> |     <column id="153" parent="20" name="entityName"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="126" parent="19" name="entityId"> |     <column id="154" parent="20" name="entityId"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="127" parent="19" name="sourceId"> |     <column id="155" parent="20" name="sourceId"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="128" parent="19" name="syncDate"> |     <column id="156" parent="20" name="syncDate"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <index id="129" parent="19" name="IDX_sync_entityName_entityId"> |     <index id="157" parent="20" name="IDX_sync_entityName_entityId"> | ||||||
|       <ColNames>entityName |       <ColNames>entityName | ||||||
| entityId</ColNames> | entityId</ColNames> | ||||||
|       <ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
| </ColumnCollations> |  | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <index id="130" parent="19" name="IDX_sync_syncDate"> |     <index id="158" parent="20" name="IDX_sync_syncDate"> | ||||||
|       <ColNames>syncDate</ColNames> |       <ColNames>syncDate</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <key id="131" parent="19"> |     <key id="159" parent="20"> | ||||||
|       <ColNames>id</ColNames> |       <ColNames>id</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|     </key> |     </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 | ||||||
|  |  | ||||||
|  | sudo docker build -t zadam/trilium:latest -t zadam/trilium:$1 . | ||||||
							
								
								
									
										16
									
								
								bin/build-pkg.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | |||||||
|  | #!/usr/bin/env bash | ||||||
|  |  | ||||||
|  | PKG_DIR=dist/trilium-linux-x64-server | ||||||
|  |  | ||||||
|  | mkdir $PKG_DIR | ||||||
|  |  | ||||||
|  | pkg . --targets node8-linux-x64 --output ${PKG_DIR}/trilium | ||||||
|  |  | ||||||
|  | chmod +x ${PKG_DIR}/trilium | ||||||
|  |  | ||||||
|  | cp node_modules/sqlite3/lib/binding/node-v57-linux-x64/node_sqlite3.node ${PKG_DIR}/ | ||||||
|  | cp node_modules/scrypt/build/Release/scrypt.node ${PKG_DIR}/ | ||||||
|  |  | ||||||
|  | cd dist | ||||||
|  |  | ||||||
|  | 7z a trilium-linux-x64-server.7z trilium-linux-x64-server | ||||||
							
								
								
									
										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 | ||||||
|  |  | ||||||
|  | sudo docker push zadam/trilium:latest | ||||||
|  | sudo docker push zadam/trilium:$1 | ||||||
| @@ -24,9 +24,9 @@ jq '.version = "'$VERSION'"' package.json|sponge package.json | |||||||
|  |  | ||||||
| git add 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 | TAG=v$VERSION | ||||||
|  |  | ||||||
| @@ -47,6 +47,7 @@ bin/package.sh | |||||||
| LINUX_X64_BUILD=trilium-linux-x64-$VERSION.7z | LINUX_X64_BUILD=trilium-linux-x64-$VERSION.7z | ||||||
| LINUX_IA32_BUILD=trilium-linux-ia32-$VERSION.7z | LINUX_IA32_BUILD=trilium-linux-ia32-$VERSION.7z | ||||||
| WINDOWS_X64_BUILD=trilium-windows-x64-$VERSION.7z | WINDOWS_X64_BUILD=trilium-windows-x64-$VERSION.7z | ||||||
|  | SERVER_BUILD=trilium-linux-x64-server.elf | ||||||
|  |  | ||||||
| echo "Creating release in GitHub" | echo "Creating release in GitHub" | ||||||
|  |  | ||||||
| @@ -75,4 +76,21 @@ github-release upload \ | |||||||
|     --name "$WINDOWS_X64_BUILD" \ |     --name "$WINDOWS_X64_BUILD" \ | ||||||
|     --file "dist/$WINDOWS_X64_BUILD" |     --file "dist/$WINDOWS_X64_BUILD" | ||||||
|  |  | ||||||
|  | echo "Packaging server version" | ||||||
|  |  | ||||||
|  | npm run build-pkg | ||||||
|  |  | ||||||
|  | github-release upload \ | ||||||
|  |     --tag $TAG \ | ||||||
|  |     --name "$SERVER_BUILD" \ | ||||||
|  |     --file "dist/$SERVER_BUILD" | ||||||
|  |  | ||||||
|  | echo "Building docker image" | ||||||
|  |  | ||||||
|  | bin/build-docker.sh $VERSION | ||||||
|  |  | ||||||
|  | echo "Pushing docker image to dockerhub" | ||||||
|  |  | ||||||
|  | bin/push-docker-image.sh $VERSION | ||||||
|  |  | ||||||
| echo "Release finished!" | echo "Release finished!" | ||||||
| @@ -9,9 +9,3 @@ https=false | |||||||
| # path to certificate (run "bash generate-cert.sh" to generate self-signed certificate). Relevant only if https=true | # path to certificate (run "bash generate-cert.sh" to generate self-signed certificate). Relevant only if https=true | ||||||
| certPath= | certPath= | ||||||
| keyPath= | keyPath= | ||||||
|  |  | ||||||
| [Sync] |  | ||||||
| syncServerHost= |  | ||||||
| syncServerTimeout=10000 |  | ||||||
| syncProxy= |  | ||||||
| syncServerCertificate= |  | ||||||
| @@ -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 ('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 ('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'); | 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; | ||||||
							
								
								
									
										8
									
								
								db/migrations/0101__add_sync_options.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | |||||||
|  | INSERT INTO options (name, value, dateCreated, dateModified, isSynced) | ||||||
|  | VALUES ('syncServerHost', '', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0); | ||||||
|  |  | ||||||
|  | INSERT INTO options (name, value, dateCreated, dateModified, isSynced) | ||||||
|  | VALUES ('syncServerTimeout', '5000', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0); | ||||||
|  |  | ||||||
|  | INSERT INTO options (name, value, dateCreated, dateModified, isSynced) | ||||||
|  | VALUES ('syncProxy', '', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0); | ||||||
							
								
								
									
										2
									
								
								db/migrations/0102__fix_sync_entityIds.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | |||||||
|  | DELETE FROM sync WHERE entityName = 'note_tree'; | ||||||
|  | DELETE FROM sync WHERE entityName = 'attributes'; | ||||||
							
								
								
									
										2
									
								
								db/migrations/0103__add_initialized_option.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | |||||||
|  | INSERT INTO options (name, value, dateCreated, dateModified, isSynced) | ||||||
|  | VALUES ('initialized', 'true', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0); | ||||||
							
								
								
									
										4
									
								
								db/migrations/0104__fill_sync_rows_for_options.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | |||||||
|  | const syncTableService = require('../../src/services/sync_table'); | ||||||
|  |  | ||||||
|  | // options has not been filled so far which caused problems with clean-slate sync. | ||||||
|  | module.exports = async () => await syncTableService.fillAllSyncRows(); | ||||||
| @@ -0,0 +1,2 @@ | |||||||
|  | UPDATE notes SET content = '' WHERE isDeleted = 1; | ||||||
|  | UPDATE note_revisions SET content = '' WHERE (SELECT isDeleted FROM notes WHERE noteId = note_revisions.noteId) = 1; | ||||||
							
								
								
									
										15
									
								
								db/migrations/0106__add_relations_table.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,15 @@ | |||||||
|  | CREATE TABLE relations | ||||||
|  | ( | ||||||
|  |     relationId  TEXT not null primary key, | ||||||
|  |     sourceNoteId       TEXT not null, | ||||||
|  |     name         TEXT not null, | ||||||
|  |     targetNoteId        TEXT not null, | ||||||
|  |     position     INT  default 0 not null, | ||||||
|  |     dateCreated  TEXT not null, | ||||||
|  |     dateModified TEXT not null, | ||||||
|  |     isDeleted    INT  not null | ||||||
|  |   , hash TEXT DEFAULT "" NOT NULL); | ||||||
|  | CREATE INDEX IDX_relation_sourceNoteId | ||||||
|  |   on relations (sourceNoteId); | ||||||
|  | CREATE INDEX IDX_relation_targetNoteId | ||||||
|  |   on relations (targetNoteId); | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | ALTER TABLE relations ADD isInheritable int DEFAULT 0 NULL; | ||||||
| @@ -1,8 +1,3 @@ | |||||||
| CREATE TABLE IF NOT EXISTS "options" ( |  | ||||||
|     `name`	TEXT NOT NULL PRIMARY KEY, |  | ||||||
|     `value`	TEXT, |  | ||||||
|     `dateModified` INT, |  | ||||||
|     isSynced INTEGER NOT NULL DEFAULT 0); |  | ||||||
| CREATE TABLE IF NOT EXISTS "sync" ( | CREATE TABLE IF NOT EXISTS "sync" ( | ||||||
|   `id`	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, |   `id`	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||||
|   `entityName`	TEXT NOT NULL, |   `entityName`	TEXT NOT NULL, | ||||||
| @@ -21,28 +16,6 @@ CREATE TABLE IF NOT EXISTS "source_ids" ( | |||||||
|   `dateCreated`	TEXT NOT NULL, |   `dateCreated`	TEXT NOT NULL, | ||||||
|   PRIMARY KEY(`sourceId`) |   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" ( | CREATE TABLE IF NOT EXISTS "note_revisions" ( | ||||||
|   `noteRevisionId`	TEXT NOT NULL PRIMARY KEY, |   `noteRevisionId`	TEXT NOT NULL PRIMARY KEY, | ||||||
|   `noteId`	TEXT NOT NULL, |   `noteId`	TEXT NOT NULL, | ||||||
| @@ -51,7 +24,7 @@ CREATE TABLE IF NOT EXISTS "note_revisions" ( | |||||||
|   `isProtected`	INT NOT NULL DEFAULT 0, |   `isProtected`	INT NOT NULL DEFAULT 0, | ||||||
|   `dateModifiedFrom` TEXT NOT NULL, |   `dateModifiedFrom` TEXT NOT NULL, | ||||||
|   `dateModifiedTo` TEXT NOT NULL |   `dateModifiedTo` TEXT NOT NULL | ||||||
| ); | , type TEXT DEFAULT '' NOT NULL, mime TEXT DEFAULT '' NOT NULL, hash TEXT DEFAULT "" NOT NULL); | ||||||
| CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` ( | CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` ( | ||||||
|   `noteId` |   `noteId` | ||||||
| ); | ); | ||||||
| @@ -71,7 +44,7 @@ CREATE TABLE IF NOT EXISTS "images" | |||||||
|   isDeleted INT NOT NULL DEFAULT 0, |   isDeleted INT NOT NULL DEFAULT 0, | ||||||
|   dateModified TEXT NOT NULL, |   dateModified TEXT NOT NULL, | ||||||
|   dateCreated TEXT NOT NULL |   dateCreated TEXT NOT NULL | ||||||
| ); | , hash TEXT DEFAULT "" NOT NULL); | ||||||
| CREATE TABLE note_images | CREATE TABLE note_images | ||||||
| ( | ( | ||||||
|   noteImageId TEXT PRIMARY KEY NOT NULL, |   noteImageId TEXT PRIMARY KEY NOT NULL, | ||||||
| @@ -80,7 +53,7 @@ CREATE TABLE note_images | |||||||
|   isDeleted INT NOT NULL DEFAULT 0, |   isDeleted INT NOT NULL DEFAULT 0, | ||||||
|   dateModified TEXT NOT NULL, |   dateModified TEXT NOT NULL, | ||||||
|   dateCreated TEXT NOT NULL |   dateCreated TEXT NOT NULL | ||||||
| ); | , hash TEXT DEFAULT "" NOT NULL); | ||||||
| CREATE INDEX IDX_note_images_noteId ON note_images (noteId); | CREATE INDEX IDX_note_images_noteId ON note_images (noteId); | ||||||
| CREATE INDEX IDX_note_images_imageId ON note_images (imageId); | CREATE INDEX IDX_note_images_imageId ON note_images (imageId); | ||||||
| CREATE INDEX IDX_note_images_noteId_imageId ON note_images (noteId, imageId); | CREATE INDEX IDX_note_images_noteId_imageId ON note_images (noteId, imageId); | ||||||
| @@ -90,7 +63,7 @@ CREATE TABLE IF NOT EXISTS "api_tokens" | |||||||
|   token TEXT NOT NULL, |   token TEXT NOT NULL, | ||||||
|   dateCreated TEXT NOT NULL, |   dateCreated TEXT NOT NULL, | ||||||
|   isDeleted INT NOT NULL DEFAULT 0 |   isDeleted INT NOT NULL DEFAULT 0 | ||||||
| ); | , hash TEXT DEFAULT "" NOT NULL); | ||||||
| CREATE TABLE IF NOT EXISTS "branches" ( | CREATE TABLE IF NOT EXISTS "branches" ( | ||||||
|   `branchId`	TEXT NOT NULL, |   `branchId`	TEXT NOT NULL, | ||||||
|   `noteId`	TEXT NOT NULL, |   `noteId`	TEXT NOT NULL, | ||||||
| @@ -99,7 +72,7 @@ CREATE TABLE IF NOT EXISTS "branches" ( | |||||||
|   `prefix`	TEXT, |   `prefix`	TEXT, | ||||||
|   `isExpanded`	BOOLEAN, |   `isExpanded`	BOOLEAN, | ||||||
|   `isDeleted`	INTEGER NOT NULL DEFAULT 0, |   `isDeleted`	INTEGER NOT NULL DEFAULT 0, | ||||||
|   `dateModified`	TEXT NOT NULL, |   `dateModified`	TEXT NOT NULL, hash TEXT DEFAULT "" NOT NULL, dateCreated TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z', | ||||||
|   PRIMARY KEY(`branchId`) |   PRIMARY KEY(`branchId`) | ||||||
| ); | ); | ||||||
| CREATE INDEX `IDX_branches_noteId` ON `branches` ( | CREATE INDEX `IDX_branches_noteId` ON `branches` ( | ||||||
| @@ -109,12 +82,6 @@ CREATE INDEX `IDX_branches_noteId_parentNoteId` ON `branches` ( | |||||||
|   `noteId`, |   `noteId`, | ||||||
|   `parentNoteId` |   `parentNoteId` | ||||||
| ); | ); | ||||||
| CREATE TABLE IF NOT EXISTS "recent_notes" ( |  | ||||||
|   `branchId` TEXT NOT NULL PRIMARY KEY, |  | ||||||
|   `notePath` TEXT NOT NULL, |  | ||||||
|   `dateAccessed` TEXT NOT NULL, |  | ||||||
|   isDeleted INT |  | ||||||
| ); |  | ||||||
| CREATE TABLE labels | CREATE TABLE labels | ||||||
| ( | ( | ||||||
|   labelId  TEXT not null primary key, |   labelId  TEXT not null primary key, | ||||||
| @@ -125,8 +92,61 @@ CREATE TABLE labels | |||||||
|   dateCreated  TEXT not null, |   dateCreated  TEXT not null, | ||||||
|   dateModified TEXT not null, |   dateModified TEXT not null, | ||||||
|   isDeleted    INT  not null |   isDeleted    INT  not null | ||||||
| ); | , hash TEXT DEFAULT "" NOT NULL); | ||||||
| CREATE INDEX IDX_labels_name_value | CREATE INDEX IDX_labels_name_value | ||||||
|   on labels (name, value); |   on labels (name, value); | ||||||
| CREATE INDEX IDX_labels_noteId | CREATE INDEX IDX_labels_noteId | ||||||
|   on labels (noteId); |   on labels (noteId); | ||||||
|  | CREATE TABLE IF NOT EXISTS "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 | ||||||
|  | ); | ||||||
|  | CREATE TABLE relations | ||||||
|  | ( | ||||||
|  |     relationId  TEXT not null primary key, | ||||||
|  |     sourceNoteId       TEXT not null, | ||||||
|  |     name         TEXT not null, | ||||||
|  |     targetNoteId        TEXT not null, | ||||||
|  |     isInheritable int DEFAULT 0 NULL, | ||||||
|  |     position     INT  default 0 not null, | ||||||
|  |     dateCreated  TEXT not null, | ||||||
|  |     dateModified TEXT not null, | ||||||
|  |     isDeleted    INT  not null | ||||||
|  |   , hash TEXT DEFAULT "" NOT NULL); | ||||||
|  | CREATE INDEX IDX_relation_sourceNoteId | ||||||
|  |   on relations (sourceNoteId); | ||||||
|  | CREATE INDEX IDX_relation_targetNoteId | ||||||
|  |   on relations (targetNoteId); | ||||||
|   | |||||||
							
								
								
									
										5447
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										68
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @@ -1,19 +1,22 @@ | |||||||
| { | { | ||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "description": "Trilium Notes", |   "description": "Trilium Notes", | ||||||
|   "version": "0.10.2-beta", |   "version": "0.18.0", | ||||||
|   "license": "AGPL-3.0-only", |   "license": "AGPL-3.0-only", | ||||||
|   "main": "electron.js", |   "main": "electron.js", | ||||||
|  |   "bin": { | ||||||
|  |     "trilium": "./src/www" | ||||||
|  |   }, | ||||||
|   "repository": { |   "repository": { | ||||||
|     "type": "git", |     "type": "git", | ||||||
|     "url": "https://github.com/zadam/trilium.git" |     "url": "https://github.com/zadam/trilium.git" | ||||||
|   }, |   }, | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "start": "node ./bin/www", |     "start": "node ./src/www", | ||||||
|     "test-electron": "xo", |     "test-electron": "xo", | ||||||
|     "rebuild-electron": "electron-rebuild", |     "rebuild-electron": "electron-rebuild", | ||||||
|     "start-electron": "electron . --disable-gpu", |     "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", |     "start-forge": "electron-forge start", | ||||||
|     "package-forge": "electron-forge package", |     "package-forge": "electron-forge package", | ||||||
|     "make-forge": "electron-forge make", |     "make-forge": "electron-forge make", | ||||||
| @@ -21,22 +24,20 @@ | |||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "async-mutex": "^0.1.3", |     "async-mutex": "^0.1.3", | ||||||
|     "axios": "^0.17.1", |     "axios": "^0.18", | ||||||
|     "body-parser": "~1.18.2", |     "body-parser": "^1.18.3", | ||||||
|     "cls-hooked": "^4.2.2", |     "cls-hooked": "^4.2.2", | ||||||
|     "cookie-parser": "~1.4.3", |     "cookie-parser": "~1.4.3", | ||||||
|     "debug": "~3.1.0", |     "debug": "~3.1.0", | ||||||
|     "devtron": "^1.4.0", |     "devtron": "^1.4.0", | ||||||
|     "ejs": "~2.5.7", |     "ejs": "~2.6.1", | ||||||
|     "electron": "^2.0.0-beta.5", |     "electron-debug": "^2.0.0", | ||||||
|     "electron-debug": "^1.5.0", |     "electron-dl": "^1.12.0", | ||||||
|     "electron-dl": "^1.11.0", |     "electron-in-page-search": "^1.3.2", | ||||||
|     "electron-in-page-search": "^1.2.4", |  | ||||||
|     "electron-rebuild": "^1.7.3", |  | ||||||
|     "express": "~4.16.3", |     "express": "~4.16.3", | ||||||
|     "express-session": "^1.15.6", |     "express-session": "^1.15.6", | ||||||
|     "fs-extra": "^4.0.3", |     "fs-extra": "^6.0.1", | ||||||
|     "helmet": "^3.12.0", |     "helmet": "^3.12.1", | ||||||
|     "html": "^1.0.0", |     "html": "^1.0.0", | ||||||
|     "image-type": "^3.0.0", |     "image-type": "^3.0.0", | ||||||
|     "imagemin": "^5.3.1", |     "imagemin": "^5.3.1", | ||||||
| @@ -45,30 +46,35 @@ | |||||||
|     "imagemin-pngquant": "^5.1.0", |     "imagemin-pngquant": "^5.1.0", | ||||||
|     "ini": "^1.3.5", |     "ini": "^1.3.5", | ||||||
|     "jimp": "^0.2.28", |     "jimp": "^0.2.28", | ||||||
|     "moment": "^2.21.0", |     "moment": "^2.22.2", | ||||||
|     "multer": "^1.3.0", |     "multer": "^1.3.1", | ||||||
|     "open": "0.0.5", |     "open": "0.0.5", | ||||||
|     "rand-token": "^0.4.0", |     "rand-token": "^0.4.0", | ||||||
|     "request": "^2.85.0", |     "rcedit": "^1.1.0", | ||||||
|  |     "request": "^2.87.0", | ||||||
|     "request-promise": "^4.2.2", |     "request-promise": "^4.2.2", | ||||||
|     "rimraf": "^2.6.2", |     "rimraf": "^2.6.2", | ||||||
|     "sanitize-filename": "^1.6.1", |     "sanitize-filename": "^1.6.1", | ||||||
|     "scrypt": "^6.0.3", |     "scrypt": "^6.0.3", | ||||||
|     "serve-favicon": "~2.4.5", |     "serve-favicon": "~2.5.0", | ||||||
|     "session-file-store": "^1.2.0", |     "session-file-store": "^1.2.0", | ||||||
|     "simple-node-logger": "^0.93.37", |     "simple-node-logger": "^0.93.37", | ||||||
|     "sqlite": "^2.9.1", |     "sqlite": "^2.9.2", | ||||||
|     "tar-stream": "^1.5.5", |     "tar-stream": "^1.6.1", | ||||||
|     "unescape": "^1.0.1", |     "unescape": "^1.0.1", | ||||||
|     "ws": "^3.3.3" |     "ws": "^5.2.1", | ||||||
|  |     "xml2js": "^0.4.19" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "electron-compile": "^6.4.2", |     "electron": "^2.0.4", | ||||||
|     "electron-packager": "^11.1.0", |     "electron-compile": "^6.4.3", | ||||||
|     "electron-prebuilt-compile": "2.0.0-beta.5", |     "electron-packager": "^12.1.0", | ||||||
|     "lorem-ipsum": "^1.0.4", |     "electron-prebuilt-compile": "2.0.4", | ||||||
|     "tape": "^4.9.0", |     "electron-rebuild": "^1.8.1", | ||||||
|     "xo": "^0.18.0" |     "lorem-ipsum": "^1.0.5", | ||||||
|  |     "tape": "^4.9.1", | ||||||
|  |     "xo": "^0.21.1", | ||||||
|  |     "pkg": "^4.3.3" | ||||||
|   }, |   }, | ||||||
|   "config": { |   "config": { | ||||||
|     "forge": { |     "forge": { | ||||||
| @@ -107,5 +113,15 @@ | |||||||
|       "node", |       "node", | ||||||
|       "browser" |       "browser" | ||||||
|     ] |     ] | ||||||
|  |   }, | ||||||
|  |   "pkg": { | ||||||
|  |     "assets": [ | ||||||
|  |       "./db/**/*", | ||||||
|  |       "./src/public/**/*", | ||||||
|  |       "./src/views/**/*", | ||||||
|  |       "./node_modules/mozjpeg/vendor/*", | ||||||
|  |       "./node_modules/pngquant-bin/vendor/*", | ||||||
|  |       "./node_modules/giflossy/vendor/*" | ||||||
|  |     ] | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ const sessionParser = session({ | |||||||
|     cookie: { |     cookie: { | ||||||
|         //    path: "/", |         //    path: "/", | ||||||
|         httpOnly: true, |         httpOnly: true, | ||||||
|         maxAge:  1800000 |         maxAge:  24 * 60 * 60 * 1000 // in milliseconds | ||||||
|     }, |     }, | ||||||
|     store: new FileStore({ |     store: new FileStore({ | ||||||
|         ttl: 30 * 24 * 3600, |         ttl: 30 * 24 * 3600, | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ const dateUtils = require('../services/date_utils'); | |||||||
| class ApiToken extends Entity { | class ApiToken extends Entity { | ||||||
|     static get tableName() { return "api_tokens"; } |     static get tableName() { return "api_tokens"; } | ||||||
|     static get primaryKeyName() { return "apiTokenId"; } |     static get primaryKeyName() { return "apiTokenId"; } | ||||||
|  |     static get hashedProperties() { return ["apiTokenId", "token", "dateCreated", "isDeleted"]; } | ||||||
|  |  | ||||||
|     beforeSaving() { |     beforeSaving() { | ||||||
|         super.beforeSaving(); |         super.beforeSaving(); | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ const sql = require('../services/sql'); | |||||||
| class Branch extends Entity { | class Branch extends Entity { | ||||||
|     static get tableName() { return "branches"; } |     static get tableName() { return "branches"; } | ||||||
|     static get primaryKeyName() { return "branchId"; } |     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() { |     async getNote() { | ||||||
|         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]); |         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]); | ||||||
| @@ -25,7 +27,11 @@ class Branch extends Entity { | |||||||
|             this.isDeleted = false; |             this.isDeleted = false; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.dateModified = dateUtils.nowDate() |         if (!this.dateCreated) { | ||||||
|  |             this.dateCreated = dateUtils.nowDate(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this.dateModified = dateUtils.nowDate(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| const utils = require('../services/utils'); | const utils = require('../services/utils'); | ||||||
| const repository = require('../services/repository'); |  | ||||||
|  |  | ||||||
| class Entity { | class Entity { | ||||||
|     constructor(row = {}) { |     constructor(row = {}) { | ||||||
| @@ -14,10 +13,18 @@ class Entity { | |||||||
|         if (!this[this.constructor.primaryKeyName]) { |         if (!this[this.constructor.primaryKeyName]) { | ||||||
|             this[this.constructor.primaryKeyName] = utils.newEntityId(); |             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() { |     async save() { | ||||||
|         await repository.updateEntity(this); |         await require('../services/repository').updateEntity(this); | ||||||
|  |  | ||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -4,8 +4,10 @@ const Image = require('../entities/image'); | |||||||
| const NoteImage = require('../entities/note_image'); | const NoteImage = require('../entities/note_image'); | ||||||
| const Branch = require('../entities/branch'); | const Branch = require('../entities/branch'); | ||||||
| const Label = require('../entities/label'); | const Label = require('../entities/label'); | ||||||
|  | const Relation = require('../entities/relation'); | ||||||
| const RecentNote = require('../entities/recent_note'); | const RecentNote = require('../entities/recent_note'); | ||||||
| const ApiToken = require('../entities/api_token'); | const ApiToken = require('../entities/api_token'); | ||||||
|  | const Option = require('../entities/option'); | ||||||
| const repository = require('../services/repository'); | const repository = require('../services/repository'); | ||||||
|  |  | ||||||
| function createEntityFromRow(row) { | function createEntityFromRow(row) { | ||||||
| @@ -14,6 +16,9 @@ function createEntityFromRow(row) { | |||||||
|     if (row.labelId) { |     if (row.labelId) { | ||||||
|         entity = new Label(row); |         entity = new Label(row); | ||||||
|     } |     } | ||||||
|  |     else if (row.relationId) { | ||||||
|  |         entity = new Relation(row); | ||||||
|  |     } | ||||||
|     else if (row.noteRevisionId) { |     else if (row.noteRevisionId) { | ||||||
|         entity = new NoteRevision(row); |         entity = new NoteRevision(row); | ||||||
|     } |     } | ||||||
| @@ -35,6 +40,9 @@ function createEntityFromRow(row) { | |||||||
|     else if (row.noteId) { |     else if (row.noteId) { | ||||||
|         entity = new Note(row); |         entity = new Note(row); | ||||||
|     } |     } | ||||||
|  |     else if (row.name) { | ||||||
|  |         entity = new Option(row); | ||||||
|  |     } | ||||||
|     else { |     else { | ||||||
|         throw new Error('Unknown entity type for row: ' + JSON.stringify(row)); |         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 { | class Image extends Entity { | ||||||
|     static get tableName() { return "images"; } |     static get tableName() { return "images"; } | ||||||
|     static get primaryKeyName() { return "imageId"; } |     static get primaryKeyName() { return "imageId"; } | ||||||
|  |     static get hashedProperties() { return ["imageId", "format", "checksum", "name", "isDeleted", "dateModified", "dateCreated"]; } | ||||||
|  |  | ||||||
|     beforeSaving() { |     beforeSaving() { | ||||||
|         super.beforeSaving(); |         super.beforeSaving(); | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ const sql = require('../services/sql'); | |||||||
| class Label extends Entity { | class Label extends Entity { | ||||||
|     static get tableName() { return "labels"; } |     static get tableName() { return "labels"; } | ||||||
|     static get primaryKeyName() { return "labelId"; } |     static get primaryKeyName() { return "labelId"; } | ||||||
|  |     static get hashedProperties() { return ["labelId", "noteId", "name", "value", "dateModified", "dateCreated"]; } | ||||||
|  |  | ||||||
|     async getNote() { |     async getNote() { | ||||||
|         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]); |         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]); | ||||||
|   | |||||||
| @@ -1,24 +1,33 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| const Entity = require('./entity'); | const Entity = require('./entity'); | ||||||
| const protected_session = require('../services/protected_session'); | const protectedSessionService = require('../services/protected_session'); | ||||||
| const repository = require('../services/repository'); | const repository = require('../services/repository'); | ||||||
| const dateUtils = require('../services/date_utils'); | const dateUtils = require('../services/date_utils'); | ||||||
|  |  | ||||||
| class Note extends Entity { | class Note extends Entity { | ||||||
|     static get tableName() { return "notes"; } |     static get tableName() { return "notes"; } | ||||||
|     static get primaryKeyName() { return "noteId"; } |     static get primaryKeyName() { return "noteId"; } | ||||||
|  |     static get hashedProperties() { return ["noteId", "title", "content", "type", "dateModified", "isProtected", "isDeleted"]; } | ||||||
|  |  | ||||||
|     constructor(row) { |     constructor(row) { | ||||||
|         super(row); |         super(row); | ||||||
|  |  | ||||||
|         if (this.isProtected) { |         // check if there's noteId, otherwise this is a new entity which wasn't encrypted yet | ||||||
|             protected_session.decryptNote(this); |         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); |             this.jsonContent = JSON.parse(this.content); | ||||||
|         } |         } | ||||||
|  |         catch(e) {} | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     isJson() { |     isJson() { | ||||||
| @@ -31,7 +40,7 @@ class Note extends Entity { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     isHtml() { |     isHtml() { | ||||||
|         return (this.type === "code" || this.type === "file") && this.mime === "text/html"; |         return (this.type === "code" || this.type === "file" || this.type === "render") && this.mime === "text/html"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getScriptEnv() { |     getScriptEnv() { | ||||||
| @@ -133,12 +142,12 @@ class Note extends Entity { | |||||||
|     beforeSaving() { |     beforeSaving() { | ||||||
|         super.beforeSaving(); |         super.beforeSaving(); | ||||||
|  |  | ||||||
|         if (this.isJson()) { |         if (this.isJson() && this.jsonContent) { | ||||||
|             this.content = JSON.stringify(this.jsonContent, null, '\t'); |             this.content = JSON.stringify(this.jsonContent, null, '\t'); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (this.isProtected) { |         if (this.isProtected) { | ||||||
|             protected_session.encryptNote(this); |             protectedSessionService.encryptNote(this); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (!this.isDeleted) { |         if (!this.isDeleted) { | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ const dateUtils = require('../services/date_utils'); | |||||||
| class NoteImage extends Entity { | class NoteImage extends Entity { | ||||||
|     static get tableName() { return "note_images"; } |     static get tableName() { return "note_images"; } | ||||||
|     static get primaryKeyName() { return "noteImageId"; } |     static get primaryKeyName() { return "noteImageId"; } | ||||||
|  |     static get hashedProperties() { return ["noteImageId", "noteId", "imageId", "isDeleted", "dateModified", "dateCreated"]; } | ||||||
|  |  | ||||||
|     async getNote() { |     async getNote() { | ||||||
|         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]); |         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]); | ||||||
|   | |||||||
| @@ -1,19 +1,19 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| const Entity = require('./entity'); | const Entity = require('./entity'); | ||||||
| const protected_session = require('../services/protected_session'); | const protectedSessionService = require('../services/protected_session'); | ||||||
| const utils = require('../services/utils'); |  | ||||||
| const repository = require('../services/repository'); | const repository = require('../services/repository'); | ||||||
|  |  | ||||||
| class NoteRevision extends Entity { | class NoteRevision extends Entity { | ||||||
|     static get tableName() { return "note_revisions"; } |     static get tableName() { return "note_revisions"; } | ||||||
|     static get primaryKeyName() { return "noteRevisionId"; } |     static get primaryKeyName() { return "noteRevisionId"; } | ||||||
|  |     static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "content", "dateModifiedFrom", "dateModifiedTo"]; } | ||||||
|  |  | ||||||
|     constructor(row) { |     constructor(row) { | ||||||
|         super(row); |         super(row); | ||||||
|  |  | ||||||
|         if (this.isProtected) { |         if (this.isProtected) { | ||||||
|             protected_session.decryptNoteRevision(this); |             protectedSessionService.decryptNoteRevision(this); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -25,7 +25,7 @@ class NoteRevision extends Entity { | |||||||
|         super.beforeSaving(); |         super.beforeSaving(); | ||||||
|  |  | ||||||
|         if (this.isProtected) { |         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"; | "use strict"; | ||||||
|  |  | ||||||
| const Entity = require('./entity'); | const Entity = require('./entity'); | ||||||
|  | const dateUtils = require('../services/date_utils'); | ||||||
|  |  | ||||||
| class RecentNote extends Entity { | class RecentNote extends Entity { | ||||||
|     static get tableName() { return "recent_notes"; } |     static get tableName() { return "recent_notes"; } | ||||||
|     static get primaryKeyName() { return "branchId"; } |     static get primaryKeyName() { return "branchId"; } | ||||||
|  |     static get hashedProperties() { return ["branchId", "notePath", "dateCreated", "isDeleted"]; } | ||||||
|  |  | ||||||
|  |     beforeSaving() { | ||||||
|  |         super.beforeSaving(); | ||||||
|  |  | ||||||
|  |         if (!this.isDeleted) { | ||||||
|  |             this.isDeleted = false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!this.dateCreated) { | ||||||
|  |             this.dateCreated = dateUtils.nowDate(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = RecentNote; | module.exports = RecentNote; | ||||||
							
								
								
									
										44
									
								
								src/entities/relation.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,44 @@ | |||||||
|  | "use strict"; | ||||||
|  |  | ||||||
|  | const Entity = require('./entity'); | ||||||
|  | const repository = require('../services/repository'); | ||||||
|  | const dateUtils = require('../services/date_utils'); | ||||||
|  | const sql = require('../services/sql'); | ||||||
|  |  | ||||||
|  | class Relation extends Entity { | ||||||
|  |     static get tableName() { return "relations"; } | ||||||
|  |     static get primaryKeyName() { return "relationId"; } | ||||||
|  |     static get hashedProperties() { return ["relationId", "sourceNoteId", "name", "targetNoteId", "isInheritable", "dateModified", "dateCreated"]; } | ||||||
|  |  | ||||||
|  |     async getSourceNote() { | ||||||
|  |         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.sourceNoteId]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async getTargetNote() { | ||||||
|  |         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.targetNoteId]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async beforeSaving() { | ||||||
|  |         super.beforeSaving(); | ||||||
|  |  | ||||||
|  |         if (this.position === undefined) { | ||||||
|  |             this.position = 1 + await sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM relations WHERE sourceNoteId = ?`, [this.sourceNoteId]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!this.isInheritable) { | ||||||
|  |             this.isInheritable = false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!this.isDeleted) { | ||||||
|  |             this.isDeleted = false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!this.dateCreated) { | ||||||
|  |             this.dateCreated = dateUtils.nowDate(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this.dateModified = dateUtils.nowDate(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = Relation; | ||||||
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/back-24.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 511 B | 
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/clock-16.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 381 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 linkService from '../services/link.js'; | ||||||
| import noteDetailService from '../services/note_detail.js'; | import noteDetailService from '../services/note_detail.js'; | ||||||
| import treeUtils from '../services/tree_utils.js'; | import treeUtils from '../services/tree_utils.js'; | ||||||
| import autocompleteService from '../services/autocomplete.js'; | import server from "../services/server.js"; | ||||||
|  | import noteDetailText from "../services/note_detail_text.js"; | ||||||
|  |  | ||||||
| const $dialog = $("#add-link-dialog"); | const $dialog = $("#add-link-dialog"); | ||||||
| const $form = $("#add-link-form"); | const $form = $("#add-link-form"); | ||||||
| @@ -11,8 +12,10 @@ const $linkTitle = $("#link-title"); | |||||||
| const $clonePrefix = $("#clone-prefix"); | const $clonePrefix = $("#clone-prefix"); | ||||||
| const $linkTitleFormGroup = $("#add-link-title-form-group"); | const $linkTitleFormGroup = $("#add-link-title-form-group"); | ||||||
| const $prefixFormGroup = $("#add-link-prefix-form-group"); | const $prefixFormGroup = $("#add-link-prefix-form-group"); | ||||||
|  | const $linkTypeDiv = $("#add-link-type-div"); | ||||||
| const $linkTypes = $("input[name='add-link-type']"); | const $linkTypes = $("input[name='add-link-type']"); | ||||||
| const $linkTypeHtml = $linkTypes.filter('input[value="html"]'); | const $linkTypeHtml = $linkTypes.filter('input[value="html"]'); | ||||||
|  | const $showRecentNotesButton = $("#add-link-show-recent-notes"); | ||||||
|  |  | ||||||
| function setLinkType(linkType) { | function setLinkType(linkType) { | ||||||
|     $linkTypes.each(function () { |     $linkTypes.each(function () { | ||||||
| @@ -45,18 +48,39 @@ async function showDialog() { | |||||||
|     $clonePrefix.val(''); |     $clonePrefix.val(''); | ||||||
|     $linkTitle.val(''); |     $linkTitle.val(''); | ||||||
|  |  | ||||||
|     function setDefaultLinkTitle(noteId) { |     async function setDefaultLinkTitle(noteId) { | ||||||
|         const noteTitle = treeUtils.getNoteTitle(noteId); |         const noteTitle = await treeUtils.getNoteTitle(noteId); | ||||||
|  |  | ||||||
|         $linkTitle.val(noteTitle); |         $linkTitle.val(noteTitle); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     $autoComplete.autocomplete({ |     await $autoComplete.autocomplete({ | ||||||
|         source: await autocompleteService.getAutocompleteItems(), |         source: async function(request, response) { | ||||||
|  |             const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term)); | ||||||
|  |  | ||||||
|  |             if (result.length > 0) { | ||||||
|  |                 response(result.map(row => { | ||||||
|  |                     return { | ||||||
|  |                         label: row.label, | ||||||
|  |                         value: row.label + ' (' + row.value + ')' | ||||||
|  |                     } | ||||||
|  |                 })); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 response([{ | ||||||
|  |                     label: "No results", | ||||||
|  |                     value: "No results" | ||||||
|  |                 }]); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|         minLength: 0, |         minLength: 0, | ||||||
|         change: () => { |         change: async (event, ui) => { | ||||||
|             const val = $autoComplete.val(); |             if (!ui.item) { | ||||||
|             const notePath = linkService.getNodePathFromLabel(val); |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             const notePath = linkService.getNotePathFromLabel(ui.item.value); | ||||||
|  |  | ||||||
|             if (!notePath) { |             if (!notePath) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| @@ -64,24 +88,33 @@ async function showDialog() { | |||||||
|             const noteId = treeUtils.getNoteIdFromNotePath(notePath); |             const noteId = treeUtils.getNoteIdFromNotePath(notePath); | ||||||
|  |  | ||||||
|             if (noteId) { |             if (noteId) { | ||||||
|                 setDefaultLinkTitle(noteId); |                 await setDefaultLinkTitle(noteId); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         select: function (event, ui) { | ||||||
|  |             if (ui.item.value === 'No results') { | ||||||
|  |                 return false; | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         // this is called when user goes through autocomplete list with keyboard |         // 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 |         // 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 notePath = linkService.getNotePathFromLabel(ui.item.value); | ||||||
|             const noteId = treeUtils.getNoteIdFromNotePath(notePath); |             const noteId = treeUtils.getNoteIdFromNotePath(notePath); | ||||||
|  |  | ||||||
|             setDefaultLinkTitle(noteId); |             await setDefaultLinkTitle(noteId); | ||||||
|  |  | ||||||
|  |             event.preventDefault(); | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     showRecentNotes(); | ||||||
| } | } | ||||||
|  |  | ||||||
| $form.submit(() => { | $form.submit(() => { | ||||||
|     const value = $autoComplete.val(); |     const value = $autoComplete.val(); | ||||||
|  |  | ||||||
|     const notePath = linkService.getNodePathFromLabel(value); |     const notePath = linkService.getNotePathFromLabel(value); | ||||||
|     const noteId = treeUtils.getNoteIdFromNotePath(notePath); |     const noteId = treeUtils.getNoteIdFromNotePath(notePath); | ||||||
|  |  | ||||||
|     if (notePath) { |     if (notePath) { | ||||||
| @@ -92,7 +125,16 @@ $form.submit(() => { | |||||||
|  |  | ||||||
|             $dialog.dialog("close"); |             $dialog.dialog("close"); | ||||||
|  |  | ||||||
|             linkService.addLinkToEditor(linkTitle, '#' + notePath); |             const linkHref = '#' + notePath; | ||||||
|  |  | ||||||
|  |             if (hasSelection()) { | ||||||
|  |                 const editor = noteDetailText.getEditor(); | ||||||
|  |  | ||||||
|  |                 editor.execute('link', linkHref); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 linkService.addLinkToEditor(linkTitle, linkHref); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         else if (linkType === 'selected-to-current') { |         else if (linkType === 'selected-to-current') { | ||||||
|             const prefix = $clonePrefix.val(); |             const prefix = $clonePrefix.val(); | ||||||
| @@ -113,21 +155,31 @@ $form.submit(() => { | |||||||
|     return false; |     return false; | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | // returns true if user selected some text, false if there's no selection | ||||||
|  | function hasSelection() { | ||||||
|  |     const model = noteDetailText.getEditor().model; | ||||||
|  |     const selection = model.document.selection; | ||||||
|  |  | ||||||
|  |     return !selection.isCollapsed; | ||||||
|  | } | ||||||
|  |  | ||||||
| function linkTypeChanged() { | function linkTypeChanged() { | ||||||
|     const value = $linkTypes.filter(":checked").val(); |     const value = $linkTypes.filter(":checked").val(); | ||||||
|  |  | ||||||
|     if (value === 'html') { |     $linkTitleFormGroup.toggle(!hasSelection() && value === 'html'); | ||||||
|         $linkTitleFormGroup.show(); |     $prefixFormGroup.toggle(!hasSelection() && value !== 'html'); | ||||||
|         $prefixFormGroup.hide(); |  | ||||||
|     } |     $linkTypeDiv.toggle(!hasSelection()); | ||||||
|     else { | } | ||||||
|         $linkTitleFormGroup.hide(); |  | ||||||
|         $prefixFormGroup.show(); | function showRecentNotes() { | ||||||
|     } |     $autoComplete.autocomplete("search", ""); | ||||||
| } | } | ||||||
|  |  | ||||||
| $linkTypes.change(linkTypeChanged); | $linkTypes.change(linkTypeChanged); | ||||||
|  |  | ||||||
|  | $showRecentNotesButton.click(showRecentNotes); | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     showDialog |     showDialog | ||||||
| }; | }; | ||||||
| @@ -25,7 +25,7 @@ async function showDialog() { | |||||||
|  |  | ||||||
|     $treePrefixInput.val(branch.prefix).focus(); |     $treePrefixInput.val(branch.prefix).focus(); | ||||||
|  |  | ||||||
|     const noteTitle = treeUtils.getNoteTitle(currentNode.data.noteId); |     const noteTitle = await treeUtils.getNoteTitle(currentNode.data.noteId); | ||||||
|  |  | ||||||
|     $noteTitle.html(noteTitle); |     $noteTitle.html(noteTitle); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,10 +19,10 @@ async function showDialog() { | |||||||
|     $list.html(''); |     $list.html(''); | ||||||
|  |  | ||||||
|     for (const event of result) { |     for (const event of result) { | ||||||
|         const dateTime = utils.formatDateTime(utils.parseDate(event.dateAdded)); |         const dateTime = utils.formatDateTime(utils.parseDate(event.dateCreated)); | ||||||
|  |  | ||||||
|         if (event.noteId) { |         if (event.noteId) { | ||||||
|             const noteLink = linkService.createNoteLink(event.noteId).prop('outerHTML'); |             const noteLink = await linkService.createNoteLink(event.noteId).prop('outerHTML'); | ||||||
|  |  | ||||||
|             event.comment = event.comment.replace('<note>', noteLink); |             event.comment = event.comment.replace('<note>', noteLink); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| import treeService from '../services/tree.js'; | import treeService from '../services/tree.js'; | ||||||
| import linkService from '../services/link.js'; | import server from '../services/server.js'; | ||||||
| import utils from '../services/utils.js'; | import searchNotesService from '../services/search_notes.js'; | ||||||
| import autocompleteService from '../services/autocomplete.js'; |  | ||||||
|  |  | ||||||
| const $dialog = $("#jump-to-note-dialog"); | const $dialog = $("#jump-to-note-dialog"); | ||||||
| const $autoComplete = $("#jump-to-note-autocomplete"); | const $autoComplete = $("#jump-to-note-autocomplete"); | ||||||
| const $form = $("#jump-to-note-form"); | const $showInFullTextButton = $("#show-in-full-text-button"); | ||||||
|  | const $showRecentNotesButton = $("#jump-to-note-show-recent-notes"); | ||||||
|  |  | ||||||
| async function showDialog() { | async function showDialog() { | ||||||
|     glob.activeDialog = $dialog; |     glob.activeDialog = $dialog; | ||||||
| @@ -14,35 +14,66 @@ async function showDialog() { | |||||||
|  |  | ||||||
|     $dialog.dialog({ |     $dialog.dialog({ | ||||||
|         modal: true, |         modal: true, | ||||||
|         width: 800 |         width: 800, | ||||||
|  |         position: { my: "center top+100", at: "top", of: window } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     await $autoComplete.autocomplete({ |     await $autoComplete.autocomplete({ | ||||||
|         source: await utils.stopWatch("building autocomplete", autocompleteService.getAutocompleteItems), |         source: async function(request, response) { | ||||||
|         minLength: 1 |             const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term)); | ||||||
|     }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getSelectedNotePath() { |             if (result.length > 0) { | ||||||
|     const val = $autoComplete.val(); |                 response(result); | ||||||
|     return linkService.getNodePathFromLabel(val); |             } | ||||||
| } |             else { | ||||||
|  |                 response([{ | ||||||
|  |                     label: "No results", | ||||||
|  |                     value: "No results" | ||||||
|  |                 }]); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         focus: function(event, ui) { | ||||||
|  |             event.preventDefault(); | ||||||
|  |         }, | ||||||
|  |         minLength: 0, | ||||||
|  |         autoFocus: true, | ||||||
|  |         select: function (event, ui) { | ||||||
|  |             if (ui.item.value === 'No results') { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
| function goToNote() { |             treeService.activateNode(ui.item.value); | ||||||
|     const notePath = getSelectedNotePath(); |  | ||||||
|  |  | ||||||
|     if (notePath) { |  | ||||||
|         treeService.activateNode(notePath); |  | ||||||
|  |  | ||||||
|             $dialog.dialog('close'); |             $dialog.dialog('close'); | ||||||
|         } |         } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     showRecentNotes(); | ||||||
| } | } | ||||||
|  |  | ||||||
| $form.submit(() => { | function showInFullText(e) { | ||||||
|     goToNote(); |     // stop from propagating upwards (dangerous especially with ctrl+enter executable javascript notes) | ||||||
|  |     e.preventDefault(); | ||||||
|  |     e.stopPropagation(); | ||||||
|  |  | ||||||
|     return false; |     const searchText = $autoComplete.val(); | ||||||
| }); |  | ||||||
|  |     searchNotesService.resetSearch(); | ||||||
|  |     searchNotesService.showSearch(); | ||||||
|  |     searchNotesService.doSearch(searchText); | ||||||
|  |  | ||||||
|  |     $dialog.dialog('close'); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function showRecentNotes() { | ||||||
|  |     $autoComplete.autocomplete("search", ""); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | $showInFullTextButton.click(showInFullText); | ||||||
|  |  | ||||||
|  | $showRecentNotesButton.click(showRecentNotes); | ||||||
|  |  | ||||||
|  | $dialog.bind('keydown', 'ctrl+return', showInFullText); | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     showDialog |     showDialog | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import noteDetailService from '../services/note_detail.js'; | import noteDetailService from '../services/note_detail.js'; | ||||||
| import utils from '../services/utils.js'; |  | ||||||
| import server from '../services/server.js'; | import server from '../services/server.js'; | ||||||
| import infoService from "../services/info.js"; | import infoService from "../services/info.js"; | ||||||
|  |  | ||||||
| @@ -168,9 +167,9 @@ async function showDialog() { | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| ko.applyBindings(labelsModel, document.getElementById('labels-dialog')); | ko.applyBindings(labelsModel, $dialog[0]); | ||||||
|  |  | ||||||
| $(document).on('focus', '.label-name', function (e) { | $dialog.on('focus', '.label-name', function (e) { | ||||||
|     if (!$(this).hasClass("ui-autocomplete-input")) { |     if (!$(this).hasClass("ui-autocomplete-input")) { | ||||||
|         $(this).autocomplete({ |         $(this).autocomplete({ | ||||||
|             // shouldn't be required and autocomplete should just accept array of strings, but that fails |             // shouldn't be required and autocomplete should just accept array of strings, but that fails | ||||||
| @@ -188,7 +187,7 @@ $(document).on('focus', '.label-name', function (e) { | |||||||
|     $(this).autocomplete("search", $(this).val()); |     $(this).autocomplete("search", $(this).val()); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| $(document).on('focus', '.label-value', async function (e) { | $dialog.on('focus', '.label-value', async function (e) { | ||||||
|     if (!$(this).hasClass("ui-autocomplete-input")) { |     if (!$(this).hasClass("ui-autocomplete-input")) { | ||||||
|         const labelName = $(this).parent().parent().find('.label-name').val(); |         const labelName = $(this).parent().parent().find('.label-name').val(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -54,7 +54,13 @@ $list.on('change', () => { | |||||||
|     const revisionItem = revisionItems.find(r => r.noteRevisionId === optVal); |     const revisionItem = revisionItems.find(r => r.noteRevisionId === optVal); | ||||||
|  |  | ||||||
|     $title.html(revisionItem.title); |     $title.html(revisionItem.title); | ||||||
|  |  | ||||||
|  |     if (revisionItem.type === 'text') { | ||||||
|         $content.html(revisionItem.content); |         $content.html(revisionItem.content); | ||||||
|  |     } | ||||||
|  |     else if (revisionItem.type === 'code') { | ||||||
|  |         $content.html($("<pre>").text(revisionItem.content)); | ||||||
|  |     } | ||||||
| }); | }); | ||||||
|  |  | ||||||
| $(document).on('click', "a[action='note-revision']", event => { | $(document).on('click', "a[action='note-revision']", event => { | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ function formatNode(node, level) { | |||||||
|     const indentAfter  = new Array(level - 1).join('  '); |     const indentAfter  = new Array(level - 1).join('  '); | ||||||
|     let textNode; |     let textNode; | ||||||
|  |  | ||||||
|     for (const i = 0; i < node.children.length; i++) { |     for (let i = 0; i < node.children.length; i++) { | ||||||
|         textNode = document.createTextNode('\n' + indentBefore); |         textNode = document.createTextNode('\n' + indentBefore); | ||||||
|         node.insertBefore(textNode, node.children[i]); |         node.insertBefore(textNode, node.children[i]); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,9 +1,10 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| import protectedSessionHolder from '../services/protected_session_holder.js'; | import protectedSessionHolder from '../services/protected_session_holder.js'; | ||||||
| import utils from '../services/utils.js'; |  | ||||||
| import server from '../services/server.js'; | import server from '../services/server.js'; | ||||||
| import infoService from "../services/info.js"; | import infoService from "../services/info.js"; | ||||||
|  | import zoomService from "../services/zoom.js"; | ||||||
|  | import utils from "../services/utils.js"; | ||||||
|  |  | ||||||
| const $dialog = $("#options-dialog"); | const $dialog = $("#options-dialog"); | ||||||
| const $tabs = $("#options-tabs"); | const $tabs = $("#options-tabs"); | ||||||
| @@ -33,8 +34,8 @@ async function showDialog() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function saveOptions(optionName, optionValue) { | async function saveOptions(options) { | ||||||
|     await server.put('options/' + encodeURIComponent(optionName) + '/' + encodeURIComponent(optionValue)); |     await server.put('options', options); | ||||||
|  |  | ||||||
|     infoService.showMessage("Options change have been saved."); |     infoService.showMessage("Options change have been saved."); | ||||||
| } | } | ||||||
| @@ -44,6 +45,41 @@ export default { | |||||||
|     saveOptions |     saveOptions | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | addTabHandler((function() { | ||||||
|  |     const $themeSelect = $("#theme-select"); | ||||||
|  |     const $zoomFactorSelect = $("#zoom-factor-select"); | ||||||
|  |     const $html = $("html"); | ||||||
|  |  | ||||||
|  |     function optionsLoaded(options) { | ||||||
|  |         $themeSelect.val(options.theme); | ||||||
|  |  | ||||||
|  |         if (utils.isElectron()) { | ||||||
|  |             $zoomFactorSelect.val(options.zoomFactor); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             $zoomFactorSelect.prop('disabled', true); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $themeSelect.change(function() { | ||||||
|  |         const newTheme = $(this).val(); | ||||||
|  |  | ||||||
|  |         $html.attr("class", "theme-" + newTheme); | ||||||
|  |  | ||||||
|  |         server.put('options/theme/' + newTheme); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     $zoomFactorSelect.change(function() { | ||||||
|  |         const newZoomFactor = $(this).val(); | ||||||
|  |  | ||||||
|  |         zoomService.setZoomFactorAndSave(newZoomFactor); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |         optionsLoaded | ||||||
|  |     }; | ||||||
|  | })()); | ||||||
|  |  | ||||||
| addTabHandler((function() { | addTabHandler((function() { | ||||||
|     const $form = $("#change-password-form"); |     const $form = $("#change-password-form"); | ||||||
|     const $oldPassword = $("#old-password"); |     const $oldPassword = $("#old-password"); | ||||||
| @@ -93,16 +129,15 @@ addTabHandler((function() { | |||||||
| addTabHandler((function() { | addTabHandler((function() { | ||||||
|     const $form = $("#protected-session-timeout-form"); |     const $form = $("#protected-session-timeout-form"); | ||||||
|     const $protectedSessionTimeout = $("#protected-session-timeout-in-seconds"); |     const $protectedSessionTimeout = $("#protected-session-timeout-in-seconds"); | ||||||
|     const optionName = 'protectedSessionTimeout'; |  | ||||||
|  |  | ||||||
|     function optionsLoaded(options) { |     function optionsLoaded(options) { | ||||||
|         $protectedSessionTimeout.val(options[optionName]); |         $protectedSessionTimeout.val(options['protectedSessionTimeout']); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     $form.submit(() => { |     $form.submit(() => { | ||||||
|         const protectedSessionTimeout = $protectedSessionTimeout.val(); |         const protectedSessionTimeout = $protectedSessionTimeout.val(); | ||||||
|  |  | ||||||
|         saveOptions(optionName, protectedSessionTimeout).then(() => { |         saveOptions({ 'protectedSessionTimeout': protectedSessionTimeout }).then(() => { | ||||||
|             protectedSessionHolder.setProtectedSessionTimeout(protectedSessionTimeout); |             protectedSessionHolder.setProtectedSessionTimeout(protectedSessionTimeout); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
| @@ -117,14 +152,13 @@ addTabHandler((function() { | |||||||
| addTabHandler((function () { | addTabHandler((function () { | ||||||
|     const $form = $("#note-revision-snapshot-time-interval-form"); |     const $form = $("#note-revision-snapshot-time-interval-form"); | ||||||
|     const $timeInterval = $("#note-revision-snapshot-time-interval-in-seconds"); |     const $timeInterval = $("#note-revision-snapshot-time-interval-in-seconds"); | ||||||
|     const optionName = 'noteRevisionSnapshotTimeInterval'; |  | ||||||
|  |  | ||||||
|     function optionsLoaded(options) { |     function optionsLoaded(options) { | ||||||
|         $timeInterval.val(options[optionName]); |         $timeInterval.val(options['noteRevisionSnapshotTimeInterval']); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     $form.submit(() => { |     $form.submit(() => { | ||||||
|         saveOptions(optionName, $timeInterval.val()); |         saveOptions({ 'noteRevisionSnapshotTimeInterval': $timeInterval.val() }); | ||||||
|  |  | ||||||
|         return false; |         return false; | ||||||
|     }); |     }); | ||||||
| @@ -137,6 +171,7 @@ addTabHandler((function () { | |||||||
| addTabHandler((async function () { | addTabHandler((async function () { | ||||||
|     const $appVersion = $("#app-version"); |     const $appVersion = $("#app-version"); | ||||||
|     const $dbVersion = $("#db-version"); |     const $dbVersion = $("#db-version"); | ||||||
|  |     const $syncVersion = $("#sync-version"); | ||||||
|     const $buildDate = $("#build-date"); |     const $buildDate = $("#build-date"); | ||||||
|     const $buildRevision = $("#build-revision"); |     const $buildRevision = $("#build-revision"); | ||||||
|  |  | ||||||
| @@ -144,6 +179,7 @@ addTabHandler((async function () { | |||||||
|  |  | ||||||
|     $appVersion.html(appInfo.appVersion); |     $appVersion.html(appInfo.appVersion); | ||||||
|     $dbVersion.html(appInfo.dbVersion); |     $dbVersion.html(appInfo.dbVersion); | ||||||
|  |     $syncVersion.html(appInfo.syncVersion); | ||||||
|     $buildDate.html(appInfo.buildDate); |     $buildDate.html(appInfo.buildDate); | ||||||
|     $buildRevision.html(appInfo.buildRevision); |     $buildRevision.html(appInfo.buildRevision); | ||||||
|     $buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision); |     $buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision); | ||||||
| @@ -151,6 +187,57 @@ addTabHandler((async function () { | |||||||
|     return {}; |     return {}; | ||||||
| })()); | })()); | ||||||
|  |  | ||||||
|  | addTabHandler((function() { | ||||||
|  |     const $form = $("#sync-setup-form"); | ||||||
|  |     const $syncServerHost = $("#sync-server-host"); | ||||||
|  |     const $syncServerTimeout = $("#sync-server-timeout"); | ||||||
|  |     const $syncProxy = $("#sync-proxy"); | ||||||
|  |     const $testSyncButton = $("#test-sync-button"); | ||||||
|  |     const $syncToServerButton = $("#sync-to-server-button"); | ||||||
|  |  | ||||||
|  |     function optionsLoaded(options) { | ||||||
|  |         $syncServerHost.val(options['syncServerHost']); | ||||||
|  |         $syncServerTimeout.val(options['syncServerTimeout']); | ||||||
|  |         $syncProxy.val(options['syncProxy']); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $form.submit(() => { | ||||||
|  |         saveOptions({ | ||||||
|  |             'syncServerHost': $syncServerHost.val(), | ||||||
|  |             'syncServerTimeout': $syncServerTimeout.val(), | ||||||
|  |             'syncProxy': $syncProxy.val() | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         return false; | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     $testSyncButton.click(async () => { | ||||||
|  |         const result = await server.post('sync/test'); | ||||||
|  |  | ||||||
|  |         if (result.connection === "Success") { | ||||||
|  |             infoService.showMessage("Sync server handshake has been successful"); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             infoService.showError("Sync server handshake failed, error: " + result.error); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     $syncToServerButton.click(async () => { | ||||||
|  |         const resp = await server.post("setup/sync-to-server"); | ||||||
|  |  | ||||||
|  |         if (resp.success) { | ||||||
|  |             infoService.showMessage("Sync has been established to the server instance. It will take some time to finish."); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             infoService.showError('Sync setup failed: ' + resp.error); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |         optionsLoaded | ||||||
|  |     }; | ||||||
|  | })()); | ||||||
|  |  | ||||||
| addTabHandler((async function () { | addTabHandler((async function () { | ||||||
|     const $forceFullSyncButton = $("#force-full-sync-button"); |     const $forceFullSyncButton = $("#force-full-sync-button"); | ||||||
|     const $fillSyncRowsButton = $("#fill-sync-rows-button"); |     const $fillSyncRowsButton = $("#fill-sync-rows-button"); | ||||||
|   | |||||||
| @@ -40,7 +40,7 @@ async function showDialog() { | |||||||
|                 noteLink = change.current_title; |                 noteLink = change.current_title; | ||||||
|             } |             } | ||||||
|             else { |             else { | ||||||
|                 noteLink = linkService.createNoteLink(change.noteId, change.title); |                 noteLink = await linkService.createNoteLink(change.noteId, change.title); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             changesListEl.append($('<li>') |             changesListEl.append($('<li>') | ||||||
|   | |||||||
| @@ -1,113 +0,0 @@ | |||||||
| 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; |  | ||||||
|  |  | ||||||
|     $dialog.dialog({ |  | ||||||
|         modal: true, |  | ||||||
|         width: 800, |  | ||||||
|         height: 100, |  | ||||||
|         position: { my: "center top+100", at: "top", of: window } |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     $searchInput.val(''); |  | ||||||
|  |  | ||||||
|     // remove the current note |  | ||||||
|     const recNotes = list.filter(note => note !== treeService.getCurrentNotePath()); |  | ||||||
|     const items = []; |  | ||||||
|  |  | ||||||
|     for (const notePath of recNotes) { |  | ||||||
|         items.push({ |  | ||||||
|             label: await getNoteTitle(notePath), |  | ||||||
|             value: notePath |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     $searchInput.autocomplete({ |  | ||||||
|         source: items, |  | ||||||
|         minLength: 0, |  | ||||||
|         autoFocus: true, |  | ||||||
|         select: function (event, ui) { |  | ||||||
|             treeService.activateNode(ui.item.value); |  | ||||||
|  |  | ||||||
|             $searchInput.autocomplete('destroy'); |  | ||||||
|             $dialog.dialog('close'); |  | ||||||
|         }, |  | ||||||
|         focus: function (event, ui) { |  | ||||||
|             event.preventDefault(); |  | ||||||
|         }, |  | ||||||
|         close: function (event, ui) { |  | ||||||
|             if (event.keyCode === 27) { // escape closes dialog |  | ||||||
|                 $searchInput.autocomplete('destroy'); |  | ||||||
|                 $dialog.dialog('close'); |  | ||||||
|             } |  | ||||||
|             else { |  | ||||||
|                 // keep autocomplete open |  | ||||||
|                 // we're kind of abusing autocomplete to work in a way which it's not designed for |  | ||||||
|                 $searchInput.autocomplete("search", ""); |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|         create: () => $searchInput.autocomplete("search", ""), |  | ||||||
|         classes: { |  | ||||||
|             "ui-autocomplete": "recent-notes-autocomplete" |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| 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 |  | ||||||
| }; |  | ||||||
							
								
								
									
										250
									
								
								src/public/javascripts/dialogs/relations.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,250 @@ | |||||||
|  | import noteDetailService from '../services/note_detail.js'; | ||||||
|  | import server from '../services/server.js'; | ||||||
|  | import infoService from "../services/info.js"; | ||||||
|  | import linkService from "../services/link.js"; | ||||||
|  | import treeUtils from "../services/tree_utils.js"; | ||||||
|  |  | ||||||
|  | const $dialog = $("#relations-dialog"); | ||||||
|  | const $saveRelationsButton = $("#save-relations-button"); | ||||||
|  | const $relationsBody = $('#relations-table tbody'); | ||||||
|  |  | ||||||
|  | const relationsModel = new RelationsModel(); | ||||||
|  | let relationNames = []; | ||||||
|  |  | ||||||
|  | function RelationsModel() { | ||||||
|  |     const self = this; | ||||||
|  |  | ||||||
|  |     this.relations = ko.observableArray(); | ||||||
|  |  | ||||||
|  |     this.updateRelationPositions = function() { | ||||||
|  |         let position = 0; | ||||||
|  |  | ||||||
|  |         // we need to update positions by searching in the DOM, because order of the | ||||||
|  |         // relations in the viewmodel (self.relations()) stays the same | ||||||
|  |         $relationsBody.find('input[name="position"]').each(function() { | ||||||
|  |             const relation = self.getTargetRelation(this); | ||||||
|  |  | ||||||
|  |             relation().position = position++; | ||||||
|  |         }); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     async function showRelations(relations) { | ||||||
|  |         for (const relation of relations) { | ||||||
|  |             relation.targetNoteId = await treeUtils.getNoteTitle(relation.targetNoteId) + " (" + relation.targetNoteId + ")"; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         self.relations(relations.map(ko.observable)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this.loadRelations = async function() { | ||||||
|  |         const noteId = noteDetailService.getCurrentNoteId(); | ||||||
|  |  | ||||||
|  |         const relations = await server.get('notes/' + noteId + '/relations'); | ||||||
|  |  | ||||||
|  |         await showRelations(relations); | ||||||
|  |  | ||||||
|  |         addLastEmptyRow(); | ||||||
|  |  | ||||||
|  |         relationNames = await server.get('relations/names'); | ||||||
|  |  | ||||||
|  |         // relation might not be rendered immediatelly so could not focus | ||||||
|  |         setTimeout(() => $(".relation-name:last").focus(), 100); | ||||||
|  |  | ||||||
|  |         $relationsBody.sortable({ | ||||||
|  |             handle: '.handle', | ||||||
|  |             containment: $relationsBody, | ||||||
|  |             update: this.updateRelationPositions | ||||||
|  |         }); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     this.deleteRelation = function(data, event) { | ||||||
|  |         const relation = self.getTargetRelation(event.target); | ||||||
|  |         const relationData = relation(); | ||||||
|  |  | ||||||
|  |         if (relationData) { | ||||||
|  |             relationData.isDeleted = 1; | ||||||
|  |  | ||||||
|  |             relation(relationData); | ||||||
|  |  | ||||||
|  |             addLastEmptyRow(); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     function isValid() { | ||||||
|  |         for (let relations = self.relations(), i = 0; i < relations.length; i++) { | ||||||
|  |             if (self.isEmptyName(i)) { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this.save = async function() { | ||||||
|  |         // we need to defocus from input (in case of enter-triggered save) because value is updated | ||||||
|  |         // on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would | ||||||
|  |         // stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel. | ||||||
|  |         $saveRelationsButton.focus(); | ||||||
|  |  | ||||||
|  |         if (!isValid()) { | ||||||
|  |             alert("Please fix all validation errors and try saving again."); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         self.updateRelationPositions(); | ||||||
|  |  | ||||||
|  |         const noteId = noteDetailService.getCurrentNoteId(); | ||||||
|  |  | ||||||
|  |         const relationsToSave = self.relations() | ||||||
|  |             .map(relation => relation()) | ||||||
|  |             .filter(relation => relation.relationId !== "" || relation.name !== ""); | ||||||
|  |  | ||||||
|  |         relationsToSave.forEach(relation => relation.targetNoteId = treeUtils.getNoteIdFromNotePath(linkService.getNotePathFromLabel(relation.targetNoteId))); | ||||||
|  |  | ||||||
|  |         console.log(relationsToSave); | ||||||
|  |  | ||||||
|  |         const relations = await server.put('notes/' + noteId + '/relations', relationsToSave); | ||||||
|  |  | ||||||
|  |         await showRelations(relations); | ||||||
|  |  | ||||||
|  |         addLastEmptyRow(); | ||||||
|  |  | ||||||
|  |         infoService.showMessage("Relations have been saved."); | ||||||
|  |  | ||||||
|  |         noteDetailService.loadRelationList(); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     function addLastEmptyRow() { | ||||||
|  |         const relations = self.relations().filter(attr => attr().isDeleted === 0); | ||||||
|  |         const last = relations.length === 0 ? null : relations[relations.length - 1](); | ||||||
|  |  | ||||||
|  |         if (!last || last.name.trim() !== "" || last.targetNoteId !== "") { | ||||||
|  |             self.relations.push(ko.observable({ | ||||||
|  |                 relationId: '', | ||||||
|  |                 name: '', | ||||||
|  |                 targetNoteId: '', | ||||||
|  |                 isInheritable: 0, | ||||||
|  |                 isDeleted: 0, | ||||||
|  |                 position: 0 | ||||||
|  |             })); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this.relationChanged = function (data, event) { | ||||||
|  |         addLastEmptyRow(); | ||||||
|  |  | ||||||
|  |         const relation = self.getTargetRelation(event.target); | ||||||
|  |  | ||||||
|  |         relation.valueHasMutated(); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     this.isNotUnique = function(index) { | ||||||
|  |         const cur = self.relations()[index](); | ||||||
|  |  | ||||||
|  |         if (cur.name.trim() === "") { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         for (let relations = self.relations(), i = 0; i < relations.length; i++) { | ||||||
|  |             const relation = relations[i](); | ||||||
|  |  | ||||||
|  |             if (index !== i && cur.name === relation.name) { | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return false; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     this.isEmptyName = function(index) { | ||||||
|  |         const cur = self.relations()[index](); | ||||||
|  |  | ||||||
|  |         return cur.name.trim() === "" && (cur.relationId !== "" || cur.targetNoteId !== ""); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     this.getTargetRelation = function(target) { | ||||||
|  |         const context = ko.contextFor(target); | ||||||
|  |         const index = context.$index(); | ||||||
|  |  | ||||||
|  |         return self.relations()[index]; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function showDialog() { | ||||||
|  |     glob.activeDialog = $dialog; | ||||||
|  |  | ||||||
|  |     await relationsModel.loadRelations(); | ||||||
|  |  | ||||||
|  |     $dialog.dialog({ | ||||||
|  |         modal: true, | ||||||
|  |         width: 900, | ||||||
|  |         height: 500 | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ko.applyBindings(relationsModel, document.getElementById('relations-dialog')); | ||||||
|  |  | ||||||
|  | $dialog.on('focus', '.relation-name', function (e) { | ||||||
|  |     if (!$(this).hasClass("ui-autocomplete-input")) { | ||||||
|  |         $(this).autocomplete({ | ||||||
|  |             // shouldn't be required and autocomplete should just accept array of strings, but that fails | ||||||
|  |             // because we have overriden filter() function in autocomplete.js | ||||||
|  |             source: relationNames.map(relation => { | ||||||
|  |                 return { | ||||||
|  |                     label: relation, | ||||||
|  |                     value: relation | ||||||
|  |                 } | ||||||
|  |             }), | ||||||
|  |             minLength: 0 | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $(this).autocomplete("search", $(this).val()); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | async function initNoteAutocomplete($el) { | ||||||
|  |     if (!$el.hasClass("ui-autocomplete-input")) { | ||||||
|  |         await $el.autocomplete({ | ||||||
|  |             source: async function (request, response) { | ||||||
|  |                 const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term)); | ||||||
|  |  | ||||||
|  |                 if (result.length > 0) { | ||||||
|  |                     response(result.map(row => { | ||||||
|  |                         return { | ||||||
|  |                             label: row.label, | ||||||
|  |                             value: row.label + ' (' + row.value + ')' | ||||||
|  |                         } | ||||||
|  |                     })); | ||||||
|  |                 } | ||||||
|  |                 else { | ||||||
|  |                     response([{ | ||||||
|  |                         label: "No results", | ||||||
|  |                         value: "No results" | ||||||
|  |                     }]); | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             minLength: 0, | ||||||
|  |             select: function (event, ui) { | ||||||
|  |                 if (ui.item.value === 'No results') { | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | $dialog.on('focus', '.relation-target-note-id', async function () { | ||||||
|  |     await initNoteAutocomplete($(this)); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | $dialog.on('click', '.relations-show-recent-notes', async function () { | ||||||
|  |     const $autocomplete = $(this).parent().find('.relation-target-note-id'); | ||||||
|  |  | ||||||
|  |     await initNoteAutocomplete($autocomplete); | ||||||
|  |  | ||||||
|  |     $autocomplete.autocomplete("search", ""); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |     showDialog | ||||||
|  | }; | ||||||
| @@ -14,6 +14,10 @@ class Branch { | |||||||
|         return await this.treeCache.getNote(this.noteId); |         return await this.treeCache.getNote(this.noteId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     isTopLevel() { | ||||||
|  |         return this.parentNoteId === 'root'; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     get toString() { |     get toString() { | ||||||
|         return `Branch(branchId=${this.branchId})`; |         return `Branch(branchId=${this.branchId})`; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -6,9 +6,12 @@ class NoteFull extends NoteShort { | |||||||
|  |  | ||||||
|         this.content = row.content; |         this.content = row.content; | ||||||
|  |  | ||||||
|         if (this.isJson()) { |         if (this.content !== "" && this.isJson()) { | ||||||
|  |             try { | ||||||
|                 this.jsonContent = JSON.parse(this.content); |                 this.jsonContent = JSON.parse(this.content); | ||||||
|             } |             } | ||||||
|  |             catch(e) {} | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ class NoteShort { | |||||||
|         this.isProtected = row.isProtected; |         this.isProtected = row.isProtected; | ||||||
|         this.type = row.type; |         this.type = row.type; | ||||||
|         this.mime = row.mime; |         this.mime = row.mime; | ||||||
|         this.hideInAutocomplete = row.hideInAutocomplete; |         this.archived = row.archived; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     isJson() { |     isJson() { | ||||||
| @@ -14,36 +14,55 @@ class NoteShort { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async getBranches() { |     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]) { |         return this.treeCache.getBranches(branchIds); | ||||||
|             branches.push(await this.treeCache.getBranchByChildParent(this.noteId, parent.noteId)); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|         return branches; |     hasChildren() { | ||||||
|  |         return this.treeCache.children[this.noteId] | ||||||
|  |             && this.treeCache.children[this.noteId].length > 0; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async getChildBranches() { |     async getChildBranches() { | ||||||
|         const branches = []; |         if (!this.treeCache.children[this.noteId]) { | ||||||
|  |             return []; | ||||||
|         for (const child of this.treeCache.children[this.noteId]) { |  | ||||||
|             branches.push(await this.treeCache.getBranchByChildParent(child.noteId, this.noteId)); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         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] || []; |         return this.treeCache.parents[this.noteId] || []; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async getChildNotes() { |     async getParentNotes() { | ||||||
|  |         return await this.treeCache.getNotes(this.getParentNoteIds()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getChildNoteIds() { | ||||||
|         return this.treeCache.children[this.noteId] || []; |         return this.treeCache.children[this.noteId] || []; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     async getChildNotes() { | ||||||
|  |         return await this.treeCache.getNotes(this.getChildNoteIds()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     get toString() { |     get toString() { | ||||||
|         return `Note(noteId=${this.noteId}, title=${this.title})`; |         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; | export default NoteShort; | ||||||
| @@ -1,46 +0,0 @@ | |||||||
| import server from './services/server.js'; |  | ||||||
|  |  | ||||||
| $(document).ready(async () => { |  | ||||||
|     const {appDbVersion, dbVersion} = await server.get('migration'); |  | ||||||
|  |  | ||||||
|     console.log("HI", {appDbVersion, dbVersion}); |  | ||||||
|  |  | ||||||
|     if (appDbVersion === dbVersion) { |  | ||||||
|         $("#up-to-date").show(); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         $("#need-to-migrate").show(); |  | ||||||
|  |  | ||||||
|         $("#app-db-version").html(appDbVersion); |  | ||||||
|         $("#db-version").html(dbVersion); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| $("#run-migration").click(async () => { |  | ||||||
|     $("#run-migration").prop("disabled", true); |  | ||||||
|  |  | ||||||
|     $("#migration-result").show(); |  | ||||||
|  |  | ||||||
|     const result = await server.post('migration'); |  | ||||||
|  |  | ||||||
|     for (const migration of result.migrations) { |  | ||||||
|         const row = $('<tr>') |  | ||||||
|             .append($('<td>').html(migration.dbVersion)) |  | ||||||
|             .append($('<td>').html(migration.name)) |  | ||||||
|             .append($('<td>').html(migration.success ? 'Yes' : 'No')) |  | ||||||
|             .append($('<td>').html(migration.success ? 'N/A' : migration.error)); |  | ||||||
|  |  | ||||||
|         if (!migration.success) { |  | ||||||
|             row.addClass("danger"); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         $("#migration-table").append(row); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| // copy of this shortcut to be able to debug migration problems |  | ||||||
| $(document).bind('keydown', 'ctrl+shift+i', () => { |  | ||||||
|     require('electron').remote.getCurrentWindow().toggleDevTools(); |  | ||||||
|  |  | ||||||
|     return false; |  | ||||||
| }); |  | ||||||
| @@ -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 |  | ||||||
| }; |  | ||||||
							
								
								
									
										11
									
								
								src/public/javascripts/services/bootstrap.js
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -4,7 +4,6 @@ import labelsDialog from '../dialogs/labels.js'; | |||||||
| import noteRevisionsDialog from '../dialogs/note_revisions.js'; | import noteRevisionsDialog from '../dialogs/note_revisions.js'; | ||||||
| import noteSourceDialog from '../dialogs/note_source.js'; | import noteSourceDialog from '../dialogs/note_source.js'; | ||||||
| import recentChangesDialog from '../dialogs/recent_changes.js'; | import recentChangesDialog from '../dialogs/recent_changes.js'; | ||||||
| import recentNotesDialog from '../dialogs/recent_notes.js'; |  | ||||||
| import optionsDialog from '../dialogs/options.js'; | import optionsDialog from '../dialogs/options.js'; | ||||||
| import sqlConsoleDialog from '../dialogs/sql_console.js'; | import sqlConsoleDialog from '../dialogs/sql_console.js'; | ||||||
|  |  | ||||||
| @@ -17,7 +16,7 @@ import messagingService from './messaging.js'; | |||||||
| import noteDetailService from './note_detail.js'; | import noteDetailService from './note_detail.js'; | ||||||
| import noteType from './note_type.js'; | import noteType from './note_type.js'; | ||||||
| import protected_session from './protected_session.js'; | import protected_session from './protected_session.js'; | ||||||
| import searchTreeService from './search_tree.js'; | import searchNotesService from './search_notes.js'; | ||||||
| import ScriptApi from './script_api.js'; | import ScriptApi from './script_api.js'; | ||||||
| import ScriptContext from './script_context.js'; | import ScriptContext from './script_context.js'; | ||||||
| import sync from './sync.js'; | import sync from './sync.js'; | ||||||
| @@ -35,6 +34,7 @@ import libraryLoader from "./library_loader.js"; | |||||||
| // required for CKEditor image upload plugin | // required for CKEditor image upload plugin | ||||||
| window.glob.getCurrentNode = treeService.getCurrentNode; | window.glob.getCurrentNode = treeService.getCurrentNode; | ||||||
| window.glob.getHeaders = server.getHeaders; | window.glob.getHeaders = server.getHeaders; | ||||||
|  | window.glob.showAddLinkDialog = addLinkDialog.showDialog; | ||||||
|  |  | ||||||
| // required for ESLint plugin | // required for ESLint plugin | ||||||
| window.glob.getCurrentNote = noteDetailService.getCurrentNote; | window.glob.getCurrentNote = noteDetailService.getCurrentNote; | ||||||
| @@ -46,7 +46,12 @@ window.onerror = function (msg, url, lineNo, columnNo, error) { | |||||||
|  |  | ||||||
|     let message = "Uncaught error: "; |     let message = "Uncaught error: "; | ||||||
|  |  | ||||||
|     if (string.indexOf("script error") > -1){ |     if (string.includes("Cannot read property 'defaultView' of undefined")) { | ||||||
|  |         // ignore this specific error which is very common but we don't know where it comes from | ||||||
|  |         // and it seems to be harmless | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |     else if (string.includes("script error")) { | ||||||
|         message += 'No details available'; |         message += 'No details available'; | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|   | |||||||
| @@ -1,8 +1,14 @@ | |||||||
| import ScriptContext from "./script_context.js"; | import ScriptContext from "./script_context.js"; | ||||||
| import server from "./server.js"; | import server from "./server.js"; | ||||||
|  |  | ||||||
| async function executeBundle(bundle) { | async function getAndExecuteBundle(noteId, targetNote = null) { | ||||||
|     const apiContext = ScriptContext(bundle.note, bundle.allNotes); |     const bundle = await server.get('script/bundle/' + noteId); | ||||||
|  |  | ||||||
|  |     await executeBundle(bundle, targetNote); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function executeBundle(bundle, targetNote) { | ||||||
|  |     const apiContext = ScriptContext(bundle.note, bundle.allNotes, targetNote); | ||||||
|  |  | ||||||
|     return await (function () { |     return await (function () { | ||||||
|         return eval(`const apiContext = this; (async function() { ${bundle.script}\r\n})()`); |         return eval(`const apiContext = this; (async function() { ${bundle.script}\r\n})()`); | ||||||
| @@ -17,7 +23,17 @@ async function executeStartupBundles() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function executeRelationBundles(note, relationName) { | ||||||
|  |     const bundlesToRun = await server.get("script/relation/" + note.noteId + "/" + relationName); | ||||||
|  |  | ||||||
|  |     for (const bundle of bundlesToRun) { | ||||||
|  |         await executeBundle(bundle, note); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     executeBundle, |     executeBundle, | ||||||
|     executeStartupBundles |     getAndExecuteBundle, | ||||||
|  |     executeStartupBundles, | ||||||
|  |     executeRelationBundles | ||||||
| } | } | ||||||
| @@ -94,27 +94,34 @@ const contextMenuOptions = { | |||||||
|         {title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"}, |         {title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"}, | ||||||
|         {title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"}, |         {title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"}, | ||||||
|         {title: "----"}, |         {title: "----"}, | ||||||
|         {title: "Export branch", cmd: "exportBranch", uiIcon: " ui-icon-arrowthick-1-ne"}, |         {title: "Export branch", cmd: "exportBranch", uiIcon: " ui-icon-arrowthick-1-ne", children: [ | ||||||
|         {title: "Import into branch", cmd: "importBranch", uiIcon: "ui-icon-arrowthick-1-sw"}, |             {title: "Native Tar", cmd: "exportBranchToTar"}, | ||||||
|  |             {title: "OPML", cmd: "exportBranchToOpml"} | ||||||
|  |         ]}, | ||||||
|  |         {title: "Import into branch (tar, opml)", cmd: "importBranch", uiIcon: "ui-icon-arrowthick-1-sw"}, | ||||||
|         {title: "----"}, |         {title: "----"}, | ||||||
|         {title: "Collapse branch <kbd>Alt+-</kbd>", cmd: "collapseBranch", uiIcon: "ui-icon-minus"}, |         {title: "Collapse branch <kbd>Alt+-</kbd>", cmd: "collapseBranch", uiIcon: "ui-icon-minus"}, | ||||||
|         {title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"}, |         {title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"}, | ||||||
|         {title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"} |         {title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"} | ||||||
|  |  | ||||||
|     ], |     ], | ||||||
|     beforeOpen: async (event, ui) => { |     beforeOpen: async (event, ui) => { | ||||||
|         const node = $.ui.fancytree.getNode(ui.target); |         const node = $.ui.fancytree.getNode(ui.target); | ||||||
|         const branch = await treeCache.getBranch(node.data.branchId); |         const branch = await treeCache.getBranch(node.data.branchId); | ||||||
|         const note = await treeCache.getNote(node.data.noteId); |         const note = await treeCache.getNote(node.data.noteId); | ||||||
|         const parentNote = await treeCache.getNote(branch.parentNoteId); |         const parentNote = await treeCache.getNote(branch.parentNoteId); | ||||||
|  |         const isNotRoot = note.noteId !== 'root'; | ||||||
|  |  | ||||||
|         // Modify menu entries depending on node status |         // Modify menu entries depending on node status | ||||||
|         $tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0 && (!parentNote || parentNote.type !== 'search')); |         $tree.contextmenu("enableEntry", "insertNoteHere", isNotRoot && parentNote.type !== 'search'); | ||||||
|         $tree.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0 && note.type !== 'search'); |  | ||||||
|         $tree.contextmenu("enableEntry", "insertNoteHere", !parentNote || parentNote.type !== 'search'); |  | ||||||
|         $tree.contextmenu("enableEntry", "insertChildNote", note.type !== 'search'); |         $tree.contextmenu("enableEntry", "insertChildNote", note.type !== 'search'); | ||||||
|  |         $tree.contextmenu("enableEntry", "delete", isNotRoot && 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", "importBranch", note.type !== 'search'); | ||||||
|         $tree.contextmenu("enableEntry", "exportBranch", note.type !== 'search'); |         $tree.contextmenu("enableEntry", "exportBranch", note.type !== 'search'); | ||||||
|  |         $tree.contextmenu("enableEntry", "editBranchPrefix", isNotRoot && parentNote.type !== 'search'); | ||||||
|  |  | ||||||
|         // Activate node on right-click |         // Activate node on right-click | ||||||
|         node.setActive(); |         node.setActive(); | ||||||
| @@ -159,8 +166,11 @@ const contextMenuOptions = { | |||||||
|         else if (ui.cmd === "delete") { |         else if (ui.cmd === "delete") { | ||||||
|             treeChangesService.deleteNodes(treeService.getSelectedNodes(true)); |             treeChangesService.deleteNodes(treeService.getSelectedNodes(true)); | ||||||
|         } |         } | ||||||
|         else if (ui.cmd === "exportBranch") { |         else if (ui.cmd === "exportBranchToTar") { | ||||||
|             exportService.exportBranch(node.data.noteId); |             exportService.exportBranch(node.data.noteId, 'tar'); | ||||||
|  |         } | ||||||
|  |         else if (ui.cmd === "exportBranchToOpml") { | ||||||
|  |             exportService.exportBranch(node.data.noteId, 'opml'); | ||||||
|         } |         } | ||||||
|         else if (ui.cmd === "importBranch") { |         else if (ui.cmd === "importBranch") { | ||||||
|             exportService.importBranch(node.data.noteId); |             exportService.importBranch(node.data.noteId); | ||||||
|   | |||||||
| @@ -2,16 +2,18 @@ import utils from "./utils.js"; | |||||||
| import treeService from "./tree.js"; | import treeService from "./tree.js"; | ||||||
| import linkService from "./link.js"; | import linkService from "./link.js"; | ||||||
| import fileService from "./file.js"; | import fileService from "./file.js"; | ||||||
|  | import zoomService from "./zoom.js"; | ||||||
| import noteRevisionsDialog from "../dialogs/note_revisions.js"; | import noteRevisionsDialog from "../dialogs/note_revisions.js"; | ||||||
| import optionsDialog from "../dialogs/options.js"; | import optionsDialog from "../dialogs/options.js"; | ||||||
| import addLinkDialog from "../dialogs/add_link.js"; | import addLinkDialog from "../dialogs/add_link.js"; | ||||||
| import recentNotesDialog from "../dialogs/recent_notes.js"; |  | ||||||
| import jumpToNoteDialog from "../dialogs/jump_to_note.js"; | import jumpToNoteDialog from "../dialogs/jump_to_note.js"; | ||||||
| import noteSourceDialog from "../dialogs/note_source.js"; | import noteSourceDialog from "../dialogs/note_source.js"; | ||||||
| import recentChangesDialog from "../dialogs/recent_changes.js"; | import recentChangesDialog from "../dialogs/recent_changes.js"; | ||||||
| import sqlConsoleDialog from "../dialogs/sql_console.js"; | import sqlConsoleDialog from "../dialogs/sql_console.js"; | ||||||
| import searchTreeService from "./search_tree.js"; | import searchNotesService from "./search_notes.js"; | ||||||
| import labelsDialog from "../dialogs/labels.js"; | import labelsDialog from "../dialogs/labels.js"; | ||||||
|  | import relationsDialog from "../dialogs/relations.js"; | ||||||
|  | import protectedSessionService from "./protected_session.js"; | ||||||
|  |  | ||||||
| function registerEntrypoints() { | function registerEntrypoints() { | ||||||
|     // hot keys are active also inside inputs and content editables |     // hot keys are active also inside inputs and content editables | ||||||
| @@ -21,35 +23,47 @@ function registerEntrypoints() { | |||||||
|  |  | ||||||
|     utils.bindShortcut('ctrl+l', addLinkDialog.showDialog); |     utils.bindShortcut('ctrl+l', addLinkDialog.showDialog); | ||||||
|  |  | ||||||
|     $("#jump-to-note-button").click(jumpToNoteDialog.showDialog); |     $("#jump-to-note-dialog-button").click(jumpToNoteDialog.showDialog); | ||||||
|     utils.bindShortcut('ctrl+j', jumpToNoteDialog.showDialog); |     utils.bindShortcut('ctrl+j', jumpToNoteDialog.showDialog); | ||||||
|  |  | ||||||
|     $("#show-note-revisions-button").click(noteRevisionsDialog.showCurrentNoteRevisions); |     $("#show-note-revisions-button").click(noteRevisionsDialog.showCurrentNoteRevisions); | ||||||
|  |  | ||||||
|     $("#show-source-button").click(noteSourceDialog.showDialog); |     $("#show-source-button").click(noteSourceDialog.showDialog); | ||||||
|     utils.bindShortcut('ctrl+u', noteSourceDialog.showDialog); |  | ||||||
|  |  | ||||||
|     $("#recent-changes-button").click(recentChangesDialog.showDialog); |     $("#recent-changes-button").click(recentChangesDialog.showDialog); | ||||||
|  |  | ||||||
|     $("#recent-notes-button").click(recentNotesDialog.showDialog); |     $("#protected-session-on").click(protectedSessionService.enterProtectedSession); | ||||||
|     utils.bindShortcut('ctrl+e', recentNotesDialog.showDialog); |     $("#protected-session-off").click(protectedSessionService.leaveProtectedSession); | ||||||
|  |  | ||||||
|     $("#toggle-search-button").click(searchTreeService.toggleSearch); |     $("#toggle-search-button").click(searchNotesService.toggleSearch); | ||||||
|     utils.bindShortcut('ctrl+s', searchTreeService.toggleSearch); |     utils.bindShortcut('ctrl+s', searchNotesService.toggleSearch); | ||||||
|  |  | ||||||
|     $(".show-labels-button").click(labelsDialog.showDialog); |     $(".show-labels-button").click(labelsDialog.showDialog); | ||||||
|     utils.bindShortcut('alt+l', labelsDialog.showDialog); |     utils.bindShortcut('alt+l', labelsDialog.showDialog); | ||||||
|  |  | ||||||
|  |     $(".show-relations-button").click(relationsDialog.showDialog); | ||||||
|  |     utils.bindShortcut('alt+r', relationsDialog.showDialog); | ||||||
|  |  | ||||||
|     $("#options-button").click(optionsDialog.showDialog); |     $("#options-button").click(optionsDialog.showDialog); | ||||||
|  |  | ||||||
|     utils.bindShortcut('alt+o', sqlConsoleDialog.showDialog); |     utils.bindShortcut('alt+o', sqlConsoleDialog.showDialog); | ||||||
|  |  | ||||||
|     if (utils.isElectron()) { |     if (utils.isElectron()) { | ||||||
|  |         $("#history-navigation").show(); | ||||||
|  |         $("#history-back-button").click(window.history.back); | ||||||
|  |         $("#history-forward-button").click(window.history.forward); | ||||||
|  |  | ||||||
|         utils.bindShortcut('alt+left', window.history.back); |         utils.bindShortcut('alt+left', window.history.back); | ||||||
|         utils.bindShortcut('alt+right', window.history.forward); |         utils.bindShortcut('alt+right', window.history.forward); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     utils.bindShortcut('alt+m', e => $(".hide-toggle").toggleClass("suppressed")); |     utils.bindShortcut('alt+m', e => { | ||||||
|  |         $(".hide-toggle").toggle(); | ||||||
|  |  | ||||||
|  |         // when hiding switch display to block, otherwise grid still tries to display columns which shows | ||||||
|  |         // left empty column | ||||||
|  |         $("#container").css("display", $("#container").css("display") === "grid" ? "block" : "grid"); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     // hide (toggle) everything except for the note content for distraction free writing |     // hide (toggle) everything except for the note content for distraction free writing | ||||||
|     utils.bindShortcut('alt+t', e => { |     utils.bindShortcut('alt+t', e => { | ||||||
| @@ -101,28 +115,11 @@ function registerEntrypoints() { | |||||||
|         $("#note-detail-text").focus(); |         $("#note-detail-text").focus(); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     $(document).bind('keydown', 'ctrl+-', () => { |  | ||||||
|     if (utils.isElectron()) { |     if (utils.isElectron()) { | ||||||
|             const webFrame = require('electron').webFrame; |         $(document).bind('keydown', 'ctrl+-', zoomService.decreaseZoomFactor); | ||||||
|  |         $(document).bind('keydown', 'ctrl+=', zoomService.increaseZoomFactor); | ||||||
|             if (webFrame.getZoomFactor() > 0.2) { |  | ||||||
|                 webFrame.setZoomFactor(webFrame.getZoomFactor() - 0.1); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     $(document).bind('keydown', 'ctrl+=', () => { |  | ||||||
|         if (utils.isElectron()) { |  | ||||||
|             const webFrame = require('electron').webFrame; |  | ||||||
|  |  | ||||||
|             webFrame.setZoomFactor(webFrame.getZoomFactor() + 0.1); |  | ||||||
|  |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     $("#note-title").bind('keydown', 'return', () => $("#note-detail-text").focus()); |     $("#note-title").bind('keydown', 'return', () => $("#note-detail-text").focus()); | ||||||
|  |  | ||||||
|     $("#upload-file-button").click(fileService.uploadFile); |     $("#upload-file-button").click(fileService.uploadFile); | ||||||
|   | |||||||
| @@ -3,9 +3,9 @@ import protectedSessionHolder from './protected_session_holder.js'; | |||||||
| import utils from './utils.js'; | import utils from './utils.js'; | ||||||
| import server from './server.js'; | import server from './server.js'; | ||||||
|  |  | ||||||
| function exportBranch(noteId) { | function exportBranch(noteId, format) { | ||||||
|     const url = utils.getHost() + "/api/notes/" + noteId + "/export?protectedSessionId=" |     const url = utils.getHost() + "/api/notes/" + noteId + "/export/" + format + | ||||||
|         + encodeURIComponent(protectedSessionHolder.getProtectedSessionId()); |         "?protectedSessionId=" + encodeURIComponent(protectedSessionHolder.getProtectedSessionId()); | ||||||
|  |  | ||||||
|     utils.download(url); |     utils.download(url); | ||||||
| } | } | ||||||
| @@ -29,7 +29,7 @@ $("#import-upload").change(async function() { | |||||||
|         type: 'POST', |         type: 'POST', | ||||||
|         contentType: false, // NEEDED, DON'T OMIT THIS |         contentType: false, // NEEDED, DON'T OMIT THIS | ||||||
|         processData: false, // NEEDED, DON'T OMIT THIS |         processData: false, // NEEDED, DON'T OMIT THIS | ||||||
|     }); |     }).fail((xhr, status, error) => alert('Import error: ' + xhr.responseText)); | ||||||
|  |  | ||||||
|     await treeService.reload(); |     await treeService.reload(); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ function getNotePathFromLink(url) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function getNodePathFromLabel(label) { | function getNotePathFromLabel(label) { | ||||||
|     const notePathMatch = / \(([A-Za-z0-9/]+)\)/.exec(label); |     const notePathMatch = / \(([A-Za-z0-9/]+)\)/.exec(label); | ||||||
|  |  | ||||||
|     if (notePathMatch !== null) { |     if (notePathMatch !== null) { | ||||||
| @@ -23,11 +23,11 @@ function getNodePathFromLabel(label) { | |||||||
|     return null; |     return null; | ||||||
| } | } | ||||||
|  |  | ||||||
| function createNoteLink(notePath, noteTitle) { | async function createNoteLink(notePath, noteTitle) { | ||||||
|     if (!noteTitle) { |     if (!noteTitle) { | ||||||
|         const noteId = treeUtils.getNoteIdFromNotePath(notePath); |         const noteId = treeUtils.getNoteIdFromNotePath(notePath); | ||||||
|  |  | ||||||
|         noteTitle = treeUtils.getNoteTitle(noteId); |         noteTitle = await treeUtils.getNoteTitle(noteId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const noteLink = $("<a>", { |     const noteLink = $("<a>", { | ||||||
| @@ -76,9 +76,11 @@ function goToLink(e) { | |||||||
|  |  | ||||||
| function addLinkToEditor(linkTitle, linkHref) { | function addLinkToEditor(linkTitle, linkHref) { | ||||||
|     const editor = noteDetailText.getEditor(); |     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) { | function addTextToEditor(text) { | ||||||
| @@ -95,7 +97,7 @@ $(document).on('click', 'div.popover-content a, div.ui-tooltip-content a', goToL | |||||||
| $(document).on('dblclick', '#note-detail-text a', goToLink); | $(document).on('dblclick', '#note-detail-text a', goToLink); | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     getNodePathFromLabel, |     getNotePathFromLabel, | ||||||
|     getNotePathFromLink, |     getNotePathFromLink, | ||||||
|     createNoteLink, |     createNoteLink, | ||||||
|     addLinkToEditor, |     addLinkToEditor, | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import utils from './utils.js'; | import utils from './utils.js'; | ||||||
| import infoService from "./info.js"; | import infoService from "./info.js"; | ||||||
|  |  | ||||||
| const $changesToPushCount = $("#changes-to-push-count"); | const $outstandingSyncsCount = $("#outstanding-syncs-count"); | ||||||
|  |  | ||||||
| const messageHandlers = []; | const messageHandlers = []; | ||||||
|  |  | ||||||
| @@ -43,7 +43,7 @@ function handleMessage(event) { | |||||||
|             messageHandler(syncData); |             messageHandler(syncData); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         $changesToPushCount.html(message.changesToPushCount); |         $outstandingSyncsCount.html(message.outstandingSyncs); | ||||||
|     } |     } | ||||||
|     else if (message.type === 'sync-hash-check-failed') { |     else if (message.type === 'sync-hash-check-failed') { | ||||||
|         infoService.showError("Sync check failed!", 60000); |         infoService.showError("Sync check failed!", 60000); | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import treeService from './tree.js'; | import treeService from './tree.js'; | ||||||
|  | import treeUtils from './tree_utils.js'; | ||||||
| import noteTypeService from './note_type.js'; | import noteTypeService from './note_type.js'; | ||||||
| import protectedSessionService from './protected_session.js'; | import protectedSessionService from './protected_session.js'; | ||||||
| import protectedSessionHolder from './protected_session_holder.js'; | import protectedSessionHolder from './protected_session_holder.js'; | ||||||
| @@ -6,6 +7,7 @@ import utils from './utils.js'; | |||||||
| import server from './server.js'; | import server from './server.js'; | ||||||
| import messagingService from "./messaging.js"; | import messagingService from "./messaging.js"; | ||||||
| import infoService from "./info.js"; | import infoService from "./info.js"; | ||||||
|  | import linkService from "./link.js"; | ||||||
| import treeCache from "./tree_cache.js"; | import treeCache from "./tree_cache.js"; | ||||||
| import NoteFull from "../entities/note_full.js"; | import NoteFull from "../entities/note_full.js"; | ||||||
| import noteDetailCode from './note_detail_code.js'; | import noteDetailCode from './note_detail_code.js'; | ||||||
| @@ -13,6 +15,7 @@ import noteDetailText from './note_detail_text.js'; | |||||||
| import noteDetailFile from './note_detail_file.js'; | import noteDetailFile from './note_detail_file.js'; | ||||||
| import noteDetailSearch from './note_detail_search.js'; | import noteDetailSearch from './note_detail_search.js'; | ||||||
| import noteDetailRender from './note_detail_render.js'; | import noteDetailRender from './note_detail_render.js'; | ||||||
|  | import bundleService from "./bundle.js"; | ||||||
|  |  | ||||||
| const $noteTitle = $("#note-title"); | const $noteTitle = $("#note-title"); | ||||||
|  |  | ||||||
| @@ -21,9 +24,14 @@ const $noteDetailComponents = $(".note-detail-component"); | |||||||
| const $protectButton = $("#protect-button"); | const $protectButton = $("#protect-button"); | ||||||
| const $unprotectButton = $("#unprotect-button"); | const $unprotectButton = $("#unprotect-button"); | ||||||
| const $noteDetailWrapper = $("#note-detail-wrapper"); | const $noteDetailWrapper = $("#note-detail-wrapper"); | ||||||
|  | const $noteDetailComponentWrapper = $("#note-detail-component-wrapper"); | ||||||
| const $noteIdDisplay = $("#note-id-display"); | const $noteIdDisplay = $("#note-id-display"); | ||||||
| const $labelList = $("#label-list"); | const $labelList = $("#label-list"); | ||||||
| const $labelListInner = $("#label-list-inner"); | const $labelListInner = $("#label-list-inner"); | ||||||
|  | const $relationList = $("#relation-list"); | ||||||
|  | const $relationListInner = $("#relation-list-inner"); | ||||||
|  | const $childrenOverview = $("#children-overview"); | ||||||
|  | const $scriptArea = $("#note-detail-script-area"); | ||||||
|  |  | ||||||
| let currentNote = null; | let currentNote = null; | ||||||
|  |  | ||||||
| @@ -73,58 +81,50 @@ function noteChanged() { | |||||||
| async function reload() { | async function reload() { | ||||||
|     // no saving here |     // no saving here | ||||||
|  |  | ||||||
|     await loadNoteToEditor(getCurrentNoteId()); |     await loadNoteDetail(getCurrentNoteId()); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function switchToNote(noteId) { | async function switchToNote(noteId) { | ||||||
|     if (getCurrentNoteId() !== noteId) { |     if (getCurrentNoteId() !== noteId) { | ||||||
|         await saveNoteIfChanged(); |         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() { | async function saveNoteIfChanged() { | ||||||
|     if (!isNoteChanged) { |     if (!isNoteChanged) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const note = getCurrentNote(); |     await saveNote(); | ||||||
|  |  | ||||||
|     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!"); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| function setNoteBackgroundIfProtected(note) { | function setNoteBackgroundIfProtected(note) { | ||||||
|     const isProtected = !!note.isProtected; |     const isProtected = !!note.isProtected; | ||||||
|  |  | ||||||
|     $noteDetailWrapper.toggleClass("protected", isProtected); |     $noteDetailComponentWrapper.toggleClass("protected", isProtected); | ||||||
|     $protectButton.toggle(!isProtected); |     $protectButton.toggleClass("active", isProtected); | ||||||
|     $unprotectButton.toggle(isProtected); |     $unprotectButton.toggleClass("active", !isProtected); | ||||||
| } | } | ||||||
|  |  | ||||||
| let isNewNoteCreated = false; | let isNewNoteCreated = false; | ||||||
| @@ -145,7 +145,7 @@ async function handleProtectedSession() { | |||||||
|     protectedSessionService.ensureDialogIsClosed(); |     protectedSessionService.ensureDialogIsClosed(); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function loadNoteToEditor(noteId) { | async function loadNoteDetail(noteId) { | ||||||
|     currentNote = await loadNote(noteId); |     currentNote = await loadNote(noteId); | ||||||
|  |  | ||||||
|     if (isNewNoteCreated) { |     if (isNewNoteCreated) { | ||||||
| @@ -156,6 +156,8 @@ async function loadNoteToEditor(noteId) { | |||||||
|  |  | ||||||
|     $noteIdDisplay.html(noteId); |     $noteIdDisplay.html(noteId); | ||||||
|  |  | ||||||
|  |     setNoteBackgroundIfProtected(currentNote); | ||||||
|  |  | ||||||
|     await handleProtectedSession(); |     await handleProtectedSession(); | ||||||
|  |  | ||||||
|     $noteDetailWrapper.show(); |     $noteDetailWrapper.show(); | ||||||
| @@ -176,13 +178,46 @@ async function loadNoteToEditor(noteId) { | |||||||
|         noteChangeDisabled = false; |         noteChangeDisabled = false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     setNoteBackgroundIfProtected(currentNote); |  | ||||||
|     treeService.setBranchBackgroundBasedOnProtectedStatus(noteId); |     treeService.setBranchBackgroundBasedOnProtectedStatus(noteId); | ||||||
|  |  | ||||||
|     // after loading new note make sure editor is scrolled to the top |     // after loading new note make sure editor is scrolled to the top | ||||||
|     $noteDetailWrapper.scrollTop(0); |     $noteDetailWrapper.scrollTop(0); | ||||||
|  |  | ||||||
|     await loadLabelList(); |     const labels = await loadLabelList(); | ||||||
|  |  | ||||||
|  |     const hideChildrenOverview = labels.some(label => label.name === 'hideChildrenOverview'); | ||||||
|  |     await showChildrenOverview(hideChildrenOverview); | ||||||
|  |  | ||||||
|  |     await loadRelationList(); | ||||||
|  |  | ||||||
|  |     $scriptArea.html(''); | ||||||
|  |  | ||||||
|  |     await bundleService.executeRelationBundles(getCurrentNote(), 'runOnNoteView'); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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() { | async function loadLabelList() { | ||||||
| @@ -202,6 +237,31 @@ async function loadLabelList() { | |||||||
|     else { |     else { | ||||||
|         $labelList.hide(); |         $labelList.hide(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     return labels; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function loadRelationList() { | ||||||
|  |     const noteId = getCurrentNoteId(); | ||||||
|  |  | ||||||
|  |     const relations = await server.get('notes/' + noteId + '/relations'); | ||||||
|  |  | ||||||
|  |     $relationListInner.html(''); | ||||||
|  |  | ||||||
|  |     if (relations.length > 0) { | ||||||
|  |         for (const relation of relations) { | ||||||
|  |             $relationListInner.append(relation.name + " = "); | ||||||
|  |             $relationListInner.append(await linkService.createNoteLink(relation.targetNoteId)); | ||||||
|  |             $relationListInner.append(" "); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $relationList.show(); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         $relationList.hide(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return relations; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function loadNote(noteId) { | async function loadNote(noteId) { | ||||||
| @@ -245,8 +305,6 @@ setInterval(saveNoteIfChanged, 5000); | |||||||
| export default { | export default { | ||||||
|     reload, |     reload, | ||||||
|     switchToNote, |     switchToNote, | ||||||
|     updateNoteFromInputs, |  | ||||||
|     saveNoteToServer, |  | ||||||
|     setNoteBackgroundIfProtected, |     setNoteBackgroundIfProtected, | ||||||
|     loadNote, |     loadNote, | ||||||
|     getCurrentNote, |     getCurrentNote, | ||||||
| @@ -255,6 +313,8 @@ export default { | |||||||
|     newNoteCreated, |     newNoteCreated, | ||||||
|     focus, |     focus, | ||||||
|     loadLabelList, |     loadLabelList, | ||||||
|  |     loadRelationList, | ||||||
|  |     saveNote, | ||||||
|     saveNoteIfChanged, |     saveNoteIfChanged, | ||||||
|     noteChanged |     noteChanged | ||||||
| }; | }; | ||||||
| @@ -16,6 +16,10 @@ async function show() { | |||||||
|         CodeMirror.keyMap.default["Shift-Tab"] = "indentLess"; |         CodeMirror.keyMap.default["Shift-Tab"] = "indentLess"; | ||||||
|         CodeMirror.keyMap.default["Tab"] = "indentMore"; |         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'; |         CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js'; | ||||||
|  |  | ||||||
|         codeEditor = CodeMirror($noteDetailCode[0], { |         codeEditor = CodeMirror($noteDetailCode[0], { | ||||||
| @@ -27,7 +31,8 @@ async function show() { | |||||||
|             highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false}, |             highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false}, | ||||||
|             lint: true, |             lint: true, | ||||||
|             gutters: ["CodeMirror-lint-markers"], |             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); |         codeEditor.on('change', noteDetailService.noteChanged); | ||||||
| @@ -59,16 +64,18 @@ function focus() { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function executeCurrentNote() { | async function executeCurrentNote() { | ||||||
|     if (noteDetailService.getCurrentNoteType() === 'code') { |     // ctrl+enter is also used elsewhere so make sure we're running only when appropriate | ||||||
|  |     if (noteDetailService.getCurrentNoteType() !== 'code') { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // make sure note is saved so we load latest changes |     // make sure note is saved so we load latest changes | ||||||
|     await noteDetailService.saveNoteIfChanged(); |     await noteDetailService.saveNoteIfChanged(); | ||||||
|  |  | ||||||
|     const currentNote = noteDetailService.getCurrentNote(); |     const currentNote = noteDetailService.getCurrentNote(); | ||||||
|  |  | ||||||
|     if (currentNote.mime.endsWith("env=frontend")) { |     if (currentNote.mime.endsWith("env=frontend")) { | ||||||
|             const bundle = await server.get('script/bundle/' + noteDetailService.getCurrentNoteId()); |         await bundleService.getAndExecuteBundle(noteDetailService.getCurrentNoteId()); | ||||||
|  |  | ||||||
|             bundleService.executeBundle(bundle); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (currentNote.mime.endsWith("env=backend")) { |     if (currentNote.mime.endsWith("env=backend")) { | ||||||
| @@ -76,7 +83,6 @@ async function executeCurrentNote() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     infoService.showMessage("Note executed"); |     infoService.showMessage("Note executed"); | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| $(document).bind('keydown', "ctrl+return", executeCurrentNote); | $(document).bind('keydown', "ctrl+return", executeCurrentNote); | ||||||
|   | |||||||
| @@ -1,21 +1,73 @@ | |||||||
| import bundleService from "./bundle.js"; | import bundleService from "./bundle.js"; | ||||||
| import server from "./server.js"; | import server from "./server.js"; | ||||||
| import noteDetailService from "./note_detail.js"; | import noteDetailService from "./note_detail.js"; | ||||||
|  | import noteDetailCodeService from "./note_detail_code.js"; | ||||||
|  |  | ||||||
|  | const $noteDetailCode = $('#note-detail-code'); | ||||||
| const $noteDetailRender = $('#note-detail-render'); | const $noteDetailRender = $('#note-detail-render'); | ||||||
|  | const $toggleEditButton = $('#toggle-edit-button'); | ||||||
|  | const $renderButton = $('#render-button'); | ||||||
|  |  | ||||||
|  | let codeEditorInitialized; | ||||||
|  |  | ||||||
| async function show() { | async function show() { | ||||||
|  |     codeEditorInitialized = false; | ||||||
|  |  | ||||||
|     $noteDetailRender.show(); |     $noteDetailRender.show(); | ||||||
|  |  | ||||||
|  |     await render(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function toggleEdit() { | ||||||
|  |     if ($noteDetailCode.is(":visible")) { | ||||||
|  |         $noteDetailCode.hide(); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         if (!codeEditorInitialized) { | ||||||
|  |             await noteDetailCodeService.show(); | ||||||
|  |  | ||||||
|  |             // because we can't properly scroll only the editor without scrolling the rendering | ||||||
|  |             // we limit its height | ||||||
|  |             $noteDetailCode.find('.CodeMirror').css('height', '300'); | ||||||
|  |  | ||||||
|  |             codeEditorInitialized = true; | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             $noteDetailCode.show(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | $toggleEditButton.click(toggleEdit); | ||||||
|  |  | ||||||
|  | $renderButton.click(render); | ||||||
|  |  | ||||||
|  | async function render() { | ||||||
|  |     // ctrl+enter is also used elsewhere so make sure we're running only when appropriate | ||||||
|  |     if (noteDetailService.getCurrentNoteType() !== 'render') { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (codeEditorInitialized) { | ||||||
|  |         await noteDetailService.saveNoteIfChanged(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     const bundle = await server.get('script/bundle/' + noteDetailService.getCurrentNoteId()); |     const bundle = await server.get('script/bundle/' + noteDetailService.getCurrentNoteId()); | ||||||
|  |  | ||||||
|     $noteDetailRender.html(bundle.html); |     $noteDetailRender.html(bundle.html); | ||||||
|  |  | ||||||
|  |     // if the note is empty, it doesn't make sense to do render-only since nothing will be rendered | ||||||
|  |     if (!bundle.html.trim()) { | ||||||
|  |         toggleEdit(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     await bundleService.executeBundle(bundle); |     await bundleService.executeBundle(bundle); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | $(document).bind('keydown', "ctrl+return", render); | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     show, |     show, | ||||||
|     getContent: () => null, |     getContent: noteDetailCodeService.getContent, | ||||||
|     focus: () => null |     focus: () => null | ||||||
| } | } | ||||||
| @@ -11,11 +11,16 @@ async function show() { | |||||||
|  |  | ||||||
|         textEditor = await BalloonEditor.create($noteDetailText[0], {}); |         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); | ||||||
|     textEditor.setData(noteDetailService.getCurrentNote().content || "<p></p>"); |  | ||||||
|  |  | ||||||
|     $noteDetailText.show(); |     $noteDetailText.show(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,9 +1,12 @@ | |||||||
| import treeService from './tree.js'; | import treeService from './tree.js'; | ||||||
| import noteDetail from './note_detail.js'; | import noteDetailService from './note_detail.js'; | ||||||
| import server from './server.js'; | import server from './server.js'; | ||||||
| import infoService from "./info.js"; | import infoService from "./info.js"; | ||||||
|  |  | ||||||
| const $executeScriptButton = $("#execute-script-button"); | const $executeScriptButton = $("#execute-script-button"); | ||||||
|  | const $toggleEditButton = $('#toggle-edit-button'); | ||||||
|  | const $renderButton = $('#render-button'); | ||||||
|  |  | ||||||
| const noteTypeModel = new NoteTypeModel(); | const noteTypeModel = new NoteTypeModel(); | ||||||
|  |  | ||||||
| function NoteTypeModel() { | function NoteTypeModel() { | ||||||
| @@ -84,13 +87,13 @@ function NoteTypeModel() { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     async function save() { |     async function save() { | ||||||
|         const note = noteDetail.getCurrentNote(); |         const note = noteDetailService.getCurrentNote(); | ||||||
|  |  | ||||||
|         await server.put('notes/' + note.noteId |         await server.put('notes/' + note.noteId | ||||||
|             + '/type/' + encodeURIComponent(self.type()) |             + '/type/' + encodeURIComponent(self.type()) | ||||||
|             + '/mime/' + encodeURIComponent(self.mime())); |             + '/mime/' + encodeURIComponent(self.mime())); | ||||||
|  |  | ||||||
|         await noteDetail.reload(); |         await noteDetailService.reload(); | ||||||
|  |  | ||||||
|         // for the note icon to be updated in the tree |         // for the note icon to be updated in the tree | ||||||
|         await treeService.reload(); |         await treeService.reload(); | ||||||
| @@ -107,7 +110,7 @@ function NoteTypeModel() { | |||||||
|  |  | ||||||
|     this.selectRender = function() { |     this.selectRender = function() { | ||||||
|         self.type('render'); |         self.type('render'); | ||||||
|         self.mime(''); |         self.mime('text/html'); | ||||||
|  |  | ||||||
|         save(); |         save(); | ||||||
|     }; |     }; | ||||||
| @@ -128,6 +131,9 @@ function NoteTypeModel() { | |||||||
|  |  | ||||||
|     this.updateExecuteScriptButtonVisibility = function() { |     this.updateExecuteScriptButtonVisibility = function() { | ||||||
|         $executeScriptButton.toggle(self.mime().startsWith('application/javascript')); |         $executeScriptButton.toggle(self.mime().startsWith('application/javascript')); | ||||||
|  |  | ||||||
|  |         $toggleEditButton.toggle(self.type() === 'render'); | ||||||
|  |         $renderButton.toggle(self.type() === 'render'); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										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 | ||||||
|  | } | ||||||