Compare commits
	
		
			37 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 7a9542b4fc | ||
|  | 3a95c9e1bc | ||
|  | 3d2ef6be01 | ||
|  | d67246699a | ||
|  | 14c704d6db | ||
|  | 4c8eeb2e6f | ||
|  | c1b245c8b1 | ||
|  | 74202d67bb | ||
|  | 26066f39b1 | ||
|  | b255cf190c | ||
|  | bc77b143b0 | ||
|  | 9f0ff6ae7a | ||
|  | 736704c7d6 | ||
|  | 654c116c58 | ||
|  | 89a5cab98f | ||
|  | c39d0be8cd | ||
|  | e75b4cd848 | ||
|  | 378e8f35e5 | ||
|  | bdb5e2f13f | ||
|  | 8211bed449 | ||
|  | b243632483 | ||
|  | e4d2513451 | ||
|  | 385144451b | ||
|  | c8c533844e | ||
|  | 0e69f0c079 | ||
|  | aee60c444f | ||
|  | e7a504c66b | ||
|  | 45d9c7164c | ||
|  | bd913a63a8 | ||
|  | 5a1938c078 | ||
|  | 015cd68756 | ||
|  | 76c0e5b2b8 | ||
|  | 0f8f707acd | ||
|  | 083cccea28 | ||
|  | 31b76b23ce | ||
|  | af529f82e5 | ||
|  | fc6669d254 | 
							
								
								
									
										4
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | |||||||
|  | node_modules | ||||||
|  | npm-debug.log | ||||||
|  | dist | ||||||
|  | .idea | ||||||
| @@ -142,11 +142,10 @@ parentNoteId</ColNames> | |||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="42" parent="8" name="id"> |     <column id="42" 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="43" parent="8" name="noteId"> |     <column id="43" parent="8" name="noteId"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
| @@ -161,505 +160,517 @@ parentNoteId</ColNames> | |||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <key id="46" parent="8"> |     <index id="46" 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="47" parent="8"> | ||||||
|  |       <ColNames>eventId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|  |       <UnderlyingIndexName>sqlite_autoindex_event_log_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="47" parent="9" name="imageId"> |     <column id="48" 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="48" parent="9" name="format"> |     <column id="49" 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="49" parent="9" name="checksum"> |     <column id="50" 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="50" parent="9" name="name"> |     <column id="51" 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="51" parent="9" name="data"> |     <column id="52" parent="9" name="data"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>BLOB|0s</DataType> |       <DataType>BLOB|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="52" parent="9" name="isDeleted"> |     <column id="53" 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="53" parent="9" name="dateModified"> |     <column id="54" 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="54" parent="9" name="dateCreated"> |     <column id="55" 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> | ||||||
|     <column id="55" parent="9" name="hash"> |     <column id="56" parent="9" name="hash"> | ||||||
|       <Position>9</Position> |       <Position>9</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>""</DefaultExpression> |       <DefaultExpression>""</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <index id="56" parent="9" name="sqlite_autoindex_images_1"> |     <index id="57" 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="57" parent="9"> |     <key id="58" 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="58" parent="10" name="labelId"> |     <column id="59" 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="59" parent="10" name="noteId"> |     <column id="60" 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="60" parent="10" name="name"> |     <column id="61" 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="61" parent="10" name="value"> |     <column id="62" 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="62" parent="10" name="position"> |     <column id="63" 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="63" parent="10" name="dateCreated"> |     <column id="64" 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="64" parent="10" name="dateModified"> |     <column id="65" 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="65" parent="10" name="isDeleted"> |     <column id="66" 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> | ||||||
|     <column id="66" parent="10" name="hash"> |     <column id="67" parent="10" name="hash"> | ||||||
|       <Position>9</Position> |       <Position>9</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>""</DefaultExpression> |       <DefaultExpression>""</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <index id="67" parent="10" name="sqlite_autoindex_labels_1"> |     <index id="68" 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="68" parent="10" name="IDX_labels_noteId"> |     <index id="69" parent="10" name="IDX_labels_noteId"> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <index id="69" parent="10" name="IDX_labels_name_value"> |     <index id="70" parent="10" name="IDX_labels_name_value"> | ||||||
|       <ColNames>name |       <ColNames>name | ||||||
| value</ColNames> | value</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <key id="70" parent="10"> |     <key id="71" 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="71" parent="11" name="noteImageId"> |     <column id="72" 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="72" parent="11" name="noteId"> |     <column id="73" 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="73" parent="11" name="imageId"> |     <column id="74" 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="74" parent="11" name="isDeleted"> |     <column id="75" 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="75" parent="11" name="dateModified"> |     <column id="76" 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="76" parent="11" name="dateCreated"> |     <column id="77" 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> | ||||||
|     <column id="77" parent="11" name="hash"> |     <column id="78" parent="11" name="hash"> | ||||||
|       <Position>7</Position> |       <Position>7</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>""</DefaultExpression> |       <DefaultExpression>""</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <index id="78" parent="11" name="sqlite_autoindex_note_images_1"> |     <index id="79" 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="79" parent="11" name="IDX_note_images_noteId_imageId"> |     <index id="80" parent="11" name="IDX_note_images_noteId_imageId"> | ||||||
|       <ColNames>noteId |       <ColNames>noteId | ||||||
| imageId</ColNames> | imageId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <index id="80" parent="11" name="IDX_note_images_noteId"> |     <index id="81" parent="11" name="IDX_note_images_noteId"> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <index id="81" parent="11" name="IDX_note_images_imageId"> |     <index id="82" parent="11" name="IDX_note_images_imageId"> | ||||||
|       <ColNames>imageId</ColNames> |       <ColNames>imageId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <key id="82" parent="11"> |     <key id="83" 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="83" parent="12" name="noteRevisionId"> |     <column id="84" 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="84" parent="12" name="noteId"> |     <column id="85" 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="85" parent="12" name="title"> |     <column id="86" parent="12" name="title"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="86" parent="12" name="content"> |     <column id="87" parent="12" name="content"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="87" parent="12" name="isProtected"> |     <column id="88" 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="88" parent="12" name="dateModifiedFrom"> |     <column id="89" 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="89" parent="12" name="dateModifiedTo"> |     <column id="90" parent="12" name="dateModifiedTo"> | ||||||
|       <Position>7</Position> |       <Position>7</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="90" parent="12" name="type"> |     <column id="91" parent="12" name="type"> | ||||||
|       <Position>8</Position> |       <Position>8</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>''</DefaultExpression> |       <DefaultExpression>''</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="91" parent="12" name="mime"> |     <column id="92" parent="12" name="mime"> | ||||||
|       <Position>9</Position> |       <Position>9</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>''</DefaultExpression> |       <DefaultExpression>''</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="92" parent="12" name="hash"> |     <column id="93" parent="12" name="hash"> | ||||||
|       <Position>10</Position> |       <Position>10</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>""</DefaultExpression> |       <DefaultExpression>""</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <index id="93" parent="12" name="sqlite_autoindex_note_revisions_1"> |     <index id="94" 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="94" parent="12" name="IDX_note_revisions_noteId"> |     <index id="95" parent="12" name="IDX_note_revisions_noteId"> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <index id="95" parent="12" name="IDX_note_revisions_dateModifiedFrom"> |     <index id="96" parent="12" name="IDX_note_revisions_dateModifiedFrom"> | ||||||
|       <ColNames>dateModifiedFrom</ColNames> |       <ColNames>dateModifiedFrom</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <index id="96" parent="12" name="IDX_note_revisions_dateModifiedTo"> |     <index id="97" parent="12" name="IDX_note_revisions_dateModifiedTo"> | ||||||
|       <ColNames>dateModifiedTo</ColNames> |       <ColNames>dateModifiedTo</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <key id="97" parent="12"> |     <key id="98" 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="98" parent="13" name="noteId"> |     <column id="99" 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="99" parent="13" name="title"> |     <column id="100" parent="13" name="title"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>"unnamed"</DefaultExpression> |       <DefaultExpression>"unnamed"</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="100" parent="13" name="content"> |     <column id="101" parent="13" name="content"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>""</DefaultExpression> |       <DefaultExpression>""</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="101" parent="13" name="isProtected"> |     <column id="102" 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="102" parent="13" name="isDeleted"> |     <column id="103" 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="103" parent="13" name="dateCreated"> |     <column id="104" 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="104" parent="13" name="dateModified"> |     <column id="105" 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="105" parent="13" name="type"> |     <column id="106" 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="106" parent="13" name="mime"> |     <column id="107" 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> | ||||||
|     <column id="107" parent="13" name="hash"> |     <column id="108" parent="13" name="hash"> | ||||||
|       <Position>10</Position> |       <Position>10</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>""</DefaultExpression> |       <DefaultExpression>""</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <index id="108" parent="13" name="sqlite_autoindex_notes_1"> |     <index id="109" 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="109" parent="13" name="IDX_notes_type"> |     <index id="110" parent="13" name="IDX_notes_type"> | ||||||
|       <ColNames>type</ColNames> |       <ColNames>type</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <key id="110" parent="13"> |     <key id="111" 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="111" parent="14" name="name"> |     <column id="112" parent="14" name="optionId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="112" parent="14" name="value"> |     <column id="113" parent="14" name="name"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="113" parent="14" name="dateModified"> |     <column id="114" parent="14" name="value"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |     </column> | ||||||
|  |     <column id="115" parent="14" name="dateModified"> | ||||||
|  |       <Position>4</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="114" parent="14" name="isSynced"> |     <column id="116" parent="14" name="isSynced"> | ||||||
|       <Position>4</Position> |       <Position>5</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="115" parent="14" name="hash"> |     <column id="117" parent="14" name="hash"> | ||||||
|       <Position>5</Position> |       <Position>6</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>""</DefaultExpression> |       <DefaultExpression>""</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="116" parent="14" name="dateCreated"> |     <column id="118" parent="14" name="dateCreated"> | ||||||
|       <Position>6</Position> |       <Position>7</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>'1970-01-01T00:00:00.000Z'</DefaultExpression> |       <DefaultExpression>'1970-01-01T00:00:00.000Z'</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <index id="117" parent="14" name="sqlite_autoindex_options_1"> |     <index id="119" parent="14" name="sqlite_autoindex_options_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>name</ColNames> |       <ColNames>optionId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <key id="118" parent="14"> |     <key id="120" parent="14"> | ||||||
|       <ColNames>name</ColNames> |       <ColNames>optionId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="119" 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="120" 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="121" parent="15" name="dateCreated"> |  | ||||||
|       <Position>3</Position> |  | ||||||
|       <DataType>TEXT|0s</DataType> |  | ||||||
|       <NotNull>1</NotNull> |  | ||||||
|     </column> |  | ||||||
|     <column id="122" parent="15" name="isDeleted"> |  | ||||||
|       <Position>4</Position> |  | ||||||
|       <DataType>INT|0s</DataType> |  | ||||||
|     </column> |  | ||||||
|     <column id="123" parent="15" name="hash"> |     <column id="123" parent="15" name="hash"> | ||||||
|       <Position>5</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>""</DefaultExpression> |       <DefaultExpression>""</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <index id="124" parent="15" name="sqlite_autoindex_recent_notes_1"> |     <column id="124" parent="15" name="dateCreated"> | ||||||
|  |       <Position>4</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |     </column> | ||||||
|  |     <column id="125" parent="15" name="isDeleted"> | ||||||
|  |       <Position>5</Position> | ||||||
|  |       <DataType>INT|0s</DataType> | ||||||
|  |     </column> | ||||||
|  |     <index id="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="125" 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="126" parent="16" name="sourceId"> |     <column id="128" parent="16" name="sourceId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="127" parent="16" name="dateCreated"> |     <column id="129" parent="16" name="dateCreated"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <index id="128" parent="16" name="sqlite_autoindex_source_ids_1"> |     <index id="130" parent="16" name="sqlite_autoindex_source_ids_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>sourceId</ColNames> |       <ColNames>sourceId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <key id="129" parent="16"> |     <key id="131" parent="16"> | ||||||
|       <ColNames>sourceId</ColNames> |       <ColNames>sourceId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="130" parent="17" name="type"> |     <column id="132" parent="17" name="type"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>text|0s</DataType> |       <DataType>text|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="131" parent="17" name="name"> |     <column id="133" parent="17" name="name"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>text|0s</DataType> |       <DataType>text|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="132" parent="17" name="tbl_name"> |     <column id="134" parent="17" name="tbl_name"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>text|0s</DataType> |       <DataType>text|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="133" parent="17" name="rootpage"> |     <column id="135" parent="17" name="rootpage"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>integer|0s</DataType> |       <DataType>integer|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="134" parent="17" name="sql"> |     <column id="136" parent="17" name="sql"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>text|0s</DataType> |       <DataType>text|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="135" parent="18" name="name"> |     <column id="137" parent="18" name="name"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|     </column> |     </column> | ||||||
|     <column id="136" parent="18" name="seq"> |     <column id="138" parent="18" name="seq"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|     </column> |     </column> | ||||||
|     <column id="137" parent="19" name="id"> |     <column id="139" parent="19" name="id"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>INTEGER|0s</DataType> |       <DataType>INTEGER|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <SequenceIdentity>1</SequenceIdentity> |       <SequenceIdentity>1</SequenceIdentity> | ||||||
|     </column> |     </column> | ||||||
|     <column id="138" parent="19" name="entityName"> |     <column id="140" parent="19" name="entityName"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="139" parent="19" name="entityId"> |     <column id="141" parent="19" name="entityId"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="140" parent="19" name="sourceId"> |     <column id="142" parent="19" name="sourceId"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="141" parent="19" name="syncDate"> |     <column id="143" parent="19" name="syncDate"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <index id="142" parent="19" name="IDX_sync_entityName_entityId"> |     <index id="144" parent="19" name="IDX_sync_entityName_entityId"> | ||||||
|       <ColNames>entityName |       <ColNames>entityName | ||||||
| entityId</ColNames> | entityId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <index id="143" parent="19" name="IDX_sync_syncDate"> |     <index id="145" parent="19" name="IDX_sync_syncDate"> | ||||||
|       <ColNames>syncDate</ColNames> |       <ColNames>syncDate</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <key id="144" parent="19"> |     <key id="146" parent="19"> | ||||||
|       <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 | ||||||
|  |  | ||||||
|  | docker build -t zadam/trilium:latest -t zadam/trilium:$1 . | ||||||
							
								
								
									
										9
									
								
								bin/push-docker-image.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | |||||||
|  | #!/usr/bin/env bash | ||||||
|  |  | ||||||
|  | if [[ $# -eq 0 ]] ; then | ||||||
|  |     echo "Missing argument of new version" | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | docker push zadam/trilium:latest | ||||||
|  | docker push zadam/trilium:$1 | ||||||
| @@ -75,4 +75,8 @@ github-release upload \ | |||||||
|     --name "$WINDOWS_X64_BUILD" \ |     --name "$WINDOWS_X64_BUILD" \ | ||||||
|     --file "dist/$WINDOWS_X64_BUILD" |     --file "dist/$WINDOWS_X64_BUILD" | ||||||
|  |  | ||||||
|  | bin/build-docker.sh $VERSION | ||||||
|  |  | ||||||
|  | bin/push-docker-image.sh $VERSION | ||||||
|  |  | ||||||
| echo "Release finished!" | echo "Release finished!" | ||||||
							
								
								
									
										2
									
								
								db/migrations/0097__add_zoomFactor.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | |||||||
|  | INSERT INTO options (optionId, name, value, dateCreated, dateModified, isSynced) | ||||||
|  | VALUES ('zoomFactor_key', 'zoomFactor', '1.0', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0); | ||||||
							
								
								
									
										1
									
								
								db/migrations/0098__rename_hideInAutocomplete.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | UPDATE labels SET name = 'archived' WHERE name = 'hideInAutocomplete' | ||||||
							
								
								
									
										2
									
								
								db/migrations/0099__add_theme_option.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | |||||||
|  | INSERT INTO options (optionId, name, value, dateCreated, dateModified, isSynced) | ||||||
|  | VALUES ('theme_key', 'theme', 'white', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0); | ||||||
							
								
								
									
										15
									
								
								db/migrations/0100__remove_optionId.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,15 @@ | |||||||
|  | create table options_mig | ||||||
|  | ( | ||||||
|  |   name TEXT not null PRIMARY KEY, | ||||||
|  |   value TEXT, | ||||||
|  |   dateModified INT, | ||||||
|  |   isSynced INTEGER default 0 not null, | ||||||
|  |   hash TEXT default "" not null, | ||||||
|  |   dateCreated TEXT default '1970-01-01T00:00:00.000Z' not null | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | INSERT INTO options_mig (name, value, dateModified, isSynced, hash, dateCreated) | ||||||
|  | SELECT name, value, dateModified, isSynced, hash, dateCreated FROM options; | ||||||
|  |  | ||||||
|  | DROP TABLE options; | ||||||
|  | ALTER TABLE options_mig RENAME TO options; | ||||||
| @@ -1,8 +1,3 @@ | |||||||
| CREATE TABLE IF NOT EXISTS "options" ( |  | ||||||
|     `name`	TEXT NOT NULL PRIMARY KEY, |  | ||||||
|     `value`	TEXT, |  | ||||||
|     `dateModified` INT, |  | ||||||
|     isSynced INTEGER NOT NULL DEFAULT 0); |  | ||||||
| CREATE TABLE IF NOT EXISTS "sync" ( | CREATE TABLE IF NOT EXISTS "sync" ( | ||||||
|   `id`	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, |   `id`	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||||
|   `entityName`	TEXT NOT NULL, |   `entityName`	TEXT NOT NULL, | ||||||
| @@ -29,7 +24,7 @@ CREATE TABLE IF NOT EXISTS "note_revisions" ( | |||||||
|   `isProtected`	INT NOT NULL DEFAULT 0, |   `isProtected`	INT NOT NULL DEFAULT 0, | ||||||
|   `dateModifiedFrom` TEXT NOT NULL, |   `dateModifiedFrom` TEXT NOT NULL, | ||||||
|   `dateModifiedTo` TEXT NOT NULL |   `dateModifiedTo` TEXT NOT NULL | ||||||
| , type TEXT DEFAULT '' NOT NULL, mime TEXT DEFAULT '' NOT NULL); | , type TEXT DEFAULT '' NOT NULL, mime TEXT DEFAULT '' NOT NULL, hash TEXT DEFAULT "" NOT NULL); | ||||||
| CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` ( | CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` ( | ||||||
|   `noteId` |   `noteId` | ||||||
| ); | ); | ||||||
| @@ -49,7 +44,7 @@ CREATE TABLE IF NOT EXISTS "images" | |||||||
|   isDeleted INT NOT NULL DEFAULT 0, |   isDeleted INT NOT NULL DEFAULT 0, | ||||||
|   dateModified TEXT NOT NULL, |   dateModified TEXT NOT NULL, | ||||||
|   dateCreated TEXT NOT NULL |   dateCreated TEXT NOT NULL | ||||||
| ); | , hash TEXT DEFAULT "" NOT NULL); | ||||||
| CREATE TABLE note_images | CREATE TABLE note_images | ||||||
| ( | ( | ||||||
|   noteImageId TEXT PRIMARY KEY NOT NULL, |   noteImageId TEXT PRIMARY KEY NOT NULL, | ||||||
| @@ -58,7 +53,7 @@ CREATE TABLE note_images | |||||||
|   isDeleted INT NOT NULL DEFAULT 0, |   isDeleted INT NOT NULL DEFAULT 0, | ||||||
|   dateModified TEXT NOT NULL, |   dateModified TEXT NOT NULL, | ||||||
|   dateCreated TEXT NOT NULL |   dateCreated TEXT NOT NULL | ||||||
| ); | , hash TEXT DEFAULT "" NOT NULL); | ||||||
| CREATE INDEX IDX_note_images_noteId ON note_images (noteId); | CREATE INDEX IDX_note_images_noteId ON note_images (noteId); | ||||||
| CREATE INDEX IDX_note_images_imageId ON note_images (imageId); | CREATE INDEX IDX_note_images_imageId ON note_images (imageId); | ||||||
| CREATE INDEX IDX_note_images_noteId_imageId ON note_images (noteId, imageId); | CREATE INDEX IDX_note_images_noteId_imageId ON note_images (noteId, imageId); | ||||||
| @@ -68,7 +63,7 @@ CREATE TABLE IF NOT EXISTS "api_tokens" | |||||||
|   token TEXT NOT NULL, |   token TEXT NOT NULL, | ||||||
|   dateCreated TEXT NOT NULL, |   dateCreated TEXT NOT NULL, | ||||||
|   isDeleted INT NOT NULL DEFAULT 0 |   isDeleted INT NOT NULL DEFAULT 0 | ||||||
| ); | , hash TEXT DEFAULT "" NOT NULL); | ||||||
| CREATE TABLE IF NOT EXISTS "branches" ( | CREATE TABLE IF NOT EXISTS "branches" ( | ||||||
|   `branchId`	TEXT NOT NULL, |   `branchId`	TEXT NOT NULL, | ||||||
|   `noteId`	TEXT NOT NULL, |   `noteId`	TEXT NOT NULL, | ||||||
| @@ -77,7 +72,7 @@ CREATE TABLE IF NOT EXISTS "branches" ( | |||||||
|   `prefix`	TEXT, |   `prefix`	TEXT, | ||||||
|   `isExpanded`	BOOLEAN, |   `isExpanded`	BOOLEAN, | ||||||
|   `isDeleted`	INTEGER NOT NULL DEFAULT 0, |   `isDeleted`	INTEGER NOT NULL DEFAULT 0, | ||||||
|   `dateModified`	TEXT NOT NULL, |   `dateModified`	TEXT NOT NULL, hash TEXT DEFAULT "" NOT NULL, dateCreated TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z', | ||||||
|   PRIMARY KEY(`branchId`) |   PRIMARY KEY(`branchId`) | ||||||
| ); | ); | ||||||
| CREATE INDEX `IDX_branches_noteId` ON `branches` ( | CREATE INDEX `IDX_branches_noteId` ON `branches` ( | ||||||
| @@ -87,12 +82,6 @@ CREATE INDEX `IDX_branches_noteId_parentNoteId` ON `branches` ( | |||||||
|   `noteId`, |   `noteId`, | ||||||
|   `parentNoteId` |   `parentNoteId` | ||||||
| ); | ); | ||||||
| CREATE TABLE IF NOT EXISTS "recent_notes" ( |  | ||||||
|   `branchId` TEXT NOT NULL PRIMARY KEY, |  | ||||||
|   `notePath` TEXT NOT NULL, |  | ||||||
|   `dateAccessed` TEXT NOT NULL, |  | ||||||
|   isDeleted INT |  | ||||||
| ); |  | ||||||
| CREATE TABLE labels | CREATE TABLE labels | ||||||
| ( | ( | ||||||
|   labelId  TEXT not null primary key, |   labelId  TEXT not null primary key, | ||||||
| @@ -103,18 +92,11 @@ CREATE TABLE labels | |||||||
|   dateCreated  TEXT not null, |   dateCreated  TEXT not null, | ||||||
|   dateModified TEXT not null, |   dateModified TEXT not null, | ||||||
|   isDeleted    INT  not null |   isDeleted    INT  not null | ||||||
| ); | , hash TEXT DEFAULT "" NOT NULL); | ||||||
| CREATE INDEX IDX_labels_name_value | CREATE INDEX IDX_labels_name_value | ||||||
|   on labels (name, value); |   on labels (name, value); | ||||||
| CREATE INDEX IDX_labels_noteId | CREATE INDEX IDX_labels_noteId | ||||||
|   on labels (noteId); |   on labels (noteId); | ||||||
| CREATE TABLE IF NOT EXISTS "event_log" |  | ||||||
| ( |  | ||||||
|   id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, |  | ||||||
|   noteId TEXT, |  | ||||||
|   comment TEXT, |  | ||||||
|   dateAdded TEXT NOT NULL |  | ||||||
| ); |  | ||||||
| CREATE TABLE IF NOT EXISTS "notes" ( | CREATE TABLE IF NOT EXISTS "notes" ( | ||||||
|   `noteId`	TEXT NOT NULL, |   `noteId`	TEXT NOT NULL, | ||||||
|   `title`	TEXT NOT NULL DEFAULT "unnamed", |   `title`	TEXT NOT NULL DEFAULT "unnamed", | ||||||
| @@ -124,9 +106,31 @@ CREATE TABLE IF NOT EXISTS "notes" ( | |||||||
|   `dateCreated`	TEXT NOT NULL, |   `dateCreated`	TEXT NOT NULL, | ||||||
|   `dateModified`	TEXT NOT NULL, |   `dateModified`	TEXT NOT NULL, | ||||||
|   type TEXT NOT NULL DEFAULT 'text', |   type TEXT NOT NULL DEFAULT 'text', | ||||||
|   mime TEXT NOT NULL DEFAULT 'text/html', |   mime TEXT NOT NULL DEFAULT 'text/html', hash TEXT DEFAULT "" NOT NULL, | ||||||
|   PRIMARY KEY(`noteId`) |   PRIMARY KEY(`noteId`) | ||||||
| ); | ); | ||||||
| CREATE INDEX `IDX_notes_isDeleted` ON `notes` ( | CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId); | ||||||
|   `isDeleted` | CREATE INDEX IDX_notes_type | ||||||
|  |   on notes (type); | ||||||
|  | CREATE TABLE IF NOT EXISTS "recent_notes" ( | ||||||
|  |   `branchId` TEXT NOT NULL PRIMARY KEY, | ||||||
|  |   `notePath` TEXT NOT NULL, | ||||||
|  |   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 | ||||||
| ); | ); | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "description": "Trilium Notes", |   "description": "Trilium Notes", | ||||||
|   "version": "0.14.0", |   "version": "0.16.0", | ||||||
|   "license": "AGPL-3.0-only", |   "license": "AGPL-3.0-only", | ||||||
|   "main": "electron.js", |   "main": "electron.js", | ||||||
|   "repository": { |   "repository": { | ||||||
|   | |||||||
| @@ -21,10 +21,7 @@ class Entity { | |||||||
|             contentToHash += "|" + this[propertyName]; |             contentToHash += "|" + this[propertyName]; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // this IF is to ease the migration from before hashed options, can be later removed |         this["hash"] = utils.hash(contentToHash).substr(0, 10); | ||||||
|         if (this.constructor.tableName !== 'options' || this.isSynced) { |  | ||||||
|             this["hash"] = utils.hash(contentToHash).substr(0, 10); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async save() { |     async save() { | ||||||
|   | |||||||
| Before Width: | Height: | Size: 511 B After Width: | Height: | Size: 511 B | 
| Before Width: | Height: | Size: 245 B After Width: | Height: | Size: 245 B | 
| Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 339 B | 
| Before Width: | Height: | Size: 463 B After Width: | Height: | Size: 463 B | 
							
								
								
									
										
											BIN
										
									
								
								src/public/images/icons/edit-20.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 312 B | 
| Before Width: | Height: | Size: 288 B After Width: | Height: | Size: 288 B | 
| Before Width: | Height: | Size: 284 B After Width: | Height: | Size: 284 B | 
| Before Width: | Height: | Size: 292 B After Width: | Height: | Size: 292 B | 
| Before Width: | Height: | Size: 511 B 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 | 
| Before Width: | Height: | Size: 240 B 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 | 
| @@ -57,7 +57,15 @@ async function showDialog() { | |||||||
|         source: async function(request, response) { |         source: async function(request, response) { | ||||||
|             const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term)); |             const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term)); | ||||||
|  |  | ||||||
|             response(result); |             if (result.length > 0) { | ||||||
|  |                 response(result); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 response([{ | ||||||
|  |                     label: "No results", | ||||||
|  |                     value: "No results" | ||||||
|  |                 }]); | ||||||
|  |             } | ||||||
|         }, |         }, | ||||||
|         minLength: 2, |         minLength: 2, | ||||||
|         change: async () => { |         change: async () => { | ||||||
|   | |||||||
| @@ -1,10 +1,13 @@ | |||||||
| import treeService from '../services/tree.js'; | import treeService from '../services/tree.js'; | ||||||
| import linkService from '../services/link.js'; | import linkService from '../services/link.js'; | ||||||
| import server from '../services/server.js'; | import server from '../services/server.js'; | ||||||
|  | import searchNotesService from '../services/search_notes.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 $form = $("#jump-to-note-form"); | ||||||
|  | const $jumpToNoteButton = $("#jump-to-note-button"); | ||||||
|  | const $showInFullTextButton = $("#show-in-full-text-button"); | ||||||
|  |  | ||||||
| async function showDialog() { | async function showDialog() { | ||||||
|     glob.activeDialog = $dialog; |     glob.activeDialog = $dialog; | ||||||
| @@ -20,7 +23,18 @@ async function showDialog() { | |||||||
|         source: async function(request, response) { |         source: async function(request, response) { | ||||||
|             const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term)); |             const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term)); | ||||||
|  |  | ||||||
|             response(result); |             if (result.length > 0) { | ||||||
|  |                 response(result); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 response([{ | ||||||
|  |                     label: "No results", | ||||||
|  |                     value: "No results" | ||||||
|  |                 }]); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         focus: function(event, ui) { | ||||||
|  |             return $(ui.item).val() !== 'No results'; | ||||||
|         }, |         }, | ||||||
|         minLength: 2 |         minLength: 2 | ||||||
|     }); |     }); | ||||||
| @@ -41,12 +55,32 @@ function goToNote() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function showInFullText(e) { | ||||||
|  |     // stop from propagating upwards (dangerous especially with ctrl+enter executable javascript notes) | ||||||
|  |     e.preventDefault(); | ||||||
|  |     e.stopPropagation(); | ||||||
|  |  | ||||||
|  |     const searchText = $autoComplete.val(); | ||||||
|  |  | ||||||
|  |     searchNotesService.resetSearch(); | ||||||
|  |     searchNotesService.showSearch(); | ||||||
|  |     searchNotesService.doSearch(searchText); | ||||||
|  |  | ||||||
|  |     $dialog.dialog('close'); | ||||||
|  | } | ||||||
|  |  | ||||||
| $form.submit(() => { | $form.submit(() => { | ||||||
|     goToNote(); |     goToNote(); | ||||||
|  |  | ||||||
|     return false; |     return false; | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | $jumpToNoteButton.click(goToNote); | ||||||
|  |  | ||||||
|  | $showInFullTextButton.click(showInFullText); | ||||||
|  |  | ||||||
|  | $dialog.bind('keydown', 'ctrl+return', showInFullText); | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     showDialog |     showDialog | ||||||
| }; | }; | ||||||
| @@ -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"); | ||||||
| @@ -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"); | ||||||
| @@ -137,6 +173,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 +181,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); | ||||||
|   | |||||||
| @@ -1,47 +1,18 @@ | |||||||
| import treeService from '../services/tree.js'; | import treeService from '../services/tree.js'; | ||||||
| import messagingService from '../services/messaging.js'; |  | ||||||
| import server from '../services/server.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 $dialog = $("#recent-notes-dialog"); | ||||||
| const $searchInput = $('#recent-notes-search-input'); | 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) { | function addRecentNote(branchId, notePath) { | ||||||
|     setTimeout(async () => { |     setTimeout(async () => { | ||||||
|         // we include the note into recent list only if the user stayed on the note at least 5 seconds |         // we include the note into recent list only if the user stayed on the note at least 5 seconds | ||||||
|         if (notePath && notePath === treeService.getCurrentNotePath()) { |         if (notePath && notePath === treeService.getCurrentNotePath()) { | ||||||
|             const result = await server.put('recent-notes/' + branchId + '/' + encodeURIComponent(notePath)); |             const result = await server.put('recent-notes/' + branchId + '/' + encodeURIComponent(notePath)); | ||||||
|  |  | ||||||
|             list = result.map(r => r.notePath); |  | ||||||
|         } |         } | ||||||
|     }, 1500); |     }, 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() { | async function showDialog() { | ||||||
|     glob.activeDialog = $dialog; |     glob.activeDialog = $dialog; | ||||||
|  |  | ||||||
| @@ -54,16 +25,17 @@ async function showDialog() { | |||||||
|  |  | ||||||
|     $searchInput.val(''); |     $searchInput.val(''); | ||||||
|  |  | ||||||
|     // remove the current note |     const result = await server.get('recent-notes'); | ||||||
|     const recNotes = list.filter(note => note !== treeService.getCurrentNotePath()); |  | ||||||
|     const items = []; |  | ||||||
|  |  | ||||||
|     for (const notePath of recNotes) { |     // remove the current note | ||||||
|         items.push({ |     const recNotes = result.filter(note => note.notePath !== treeService.getCurrentNotePath()); | ||||||
|             label: await getNoteTitle(notePath), |  | ||||||
|             value: notePath |     const items = recNotes.map(rn => { | ||||||
|         }); |         return { | ||||||
|     } |             label: rn.title, | ||||||
|  |             value: rn.notePath | ||||||
|  |         }; | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     $searchInput.autocomplete({ |     $searchInput.autocomplete({ | ||||||
|         source: items, |         source: items, | ||||||
| @@ -96,18 +68,7 @@ async function showDialog() { | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| setTimeout(reload, 100); |  | ||||||
|  |  | ||||||
| messagingService.subscribeToMessages(syncData => { |  | ||||||
|     if (syncData.some(sync => sync.entityName === 'recent_notes')) { |  | ||||||
|         console.log(utils.now(), "Reloading recent notes because of background changes"); |  | ||||||
|  |  | ||||||
|         reload(); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     showDialog, |     showDialog, | ||||||
|     addRecentNote, |     addRecentNote | ||||||
|     reload |  | ||||||
| }; | }; | ||||||
| @@ -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() { | ||||||
| @@ -59,7 +59,7 @@ class NoteShort { | |||||||
|     get dto() { |     get dto() { | ||||||
|         const dto = Object.assign({}, this); |         const dto = Object.assign({}, this); | ||||||
|         delete dto.treeCache; |         delete dto.treeCache; | ||||||
|         delete dto.hideInAutocomplete; |         delete dto.archived; | ||||||
|  |  | ||||||
|         return dto; |         return dto; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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; |  | ||||||
| }); |  | ||||||
							
								
								
									
										2
									
								
								src/public/javascripts/services/bootstrap.js
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -17,7 +17,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'; | ||||||
|   | |||||||
| @@ -114,13 +114,14 @@ const contextMenuOptions = { | |||||||
|         // Modify menu entries depending on node status |         // Modify menu entries depending on node status | ||||||
|         $tree.contextmenu("enableEntry", "insertNoteHere", isNotRoot && parentNote.type !== 'search'); |         $tree.contextmenu("enableEntry", "insertNoteHere", isNotRoot && parentNote.type !== 'search'); | ||||||
|         $tree.contextmenu("enableEntry", "insertChildNote", note.type !== 'search'); |         $tree.contextmenu("enableEntry", "insertChildNote", note.type !== 'search'); | ||||||
|         $tree.contextmenu("enableEntry", "delete", isNotRoot); |         $tree.contextmenu("enableEntry", "delete", isNotRoot && parentNote.type !== 'search'); | ||||||
|         $tree.contextmenu("enableEntry", "copy", isNotRoot); |         $tree.contextmenu("enableEntry", "copy", isNotRoot); | ||||||
|         $tree.contextmenu("enableEntry", "cut", isNotRoot); |         $tree.contextmenu("enableEntry", "cut", isNotRoot); | ||||||
|         $tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0 && isNotRoot && parentNote.type !== 'search'); |         $tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0 && isNotRoot && parentNote.type !== 'search'); | ||||||
|         $tree.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0 && note.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", parentNote.type !== 'search'); | ||||||
|  |  | ||||||
|         // Activate node on right-click |         // Activate node on right-click | ||||||
|         node.setActive(); |         node.setActive(); | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ 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"; | ||||||
| @@ -10,7 +11,7 @@ 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 protectedSessionService from "./protected_session.js"; | import protectedSessionService from "./protected_session.js"; | ||||||
|  |  | ||||||
| @@ -22,7 +23,7 @@ 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); | ||||||
| @@ -38,8 +39,8 @@ function registerEntrypoints() { | |||||||
|     $("#recent-notes-button").click(recentNotesDialog.showDialog); |     $("#recent-notes-button").click(recentNotesDialog.showDialog); | ||||||
|     utils.bindShortcut('ctrl+e', recentNotesDialog.showDialog); |     utils.bindShortcut('ctrl+e', recentNotesDialog.showDialog); | ||||||
|  |  | ||||||
|     $("#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); | ||||||
| @@ -57,7 +58,13 @@ function registerEntrypoints() { | |||||||
|         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 => { | ||||||
| @@ -109,27 +116,10 @@ function registerEntrypoints() { | |||||||
|         $("#note-detail-text").focus(); |         $("#note-detail-text").focus(); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     $(document).bind('keydown', 'ctrl+-', () => { |     if (utils.isElectron()) { | ||||||
|         if (utils.isElectron()) { |         $(document).bind('keydown', 'ctrl+-', zoomService.decreaseZoomFactor); | ||||||
|             const webFrame = require('electron').webFrame; |         $(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()); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ 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"); | ||||||
| @@ -116,9 +117,9 @@ async function saveNoteIfChanged() { | |||||||
| 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; | ||||||
| @@ -150,6 +151,8 @@ async function loadNoteDetail(noteId) { | |||||||
|  |  | ||||||
|     $noteIdDisplay.html(noteId); |     $noteIdDisplay.html(noteId); | ||||||
|  |  | ||||||
|  |     setNoteBackgroundIfProtected(currentNote); | ||||||
|  |  | ||||||
|     await handleProtectedSession(); |     await handleProtectedSession(); | ||||||
|  |  | ||||||
|     $noteDetailWrapper.show(); |     $noteDetailWrapper.show(); | ||||||
| @@ -170,7 +173,6 @@ async function loadNoteDetail(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 | ||||||
|   | |||||||
| @@ -11,7 +11,13 @@ async function show() { | |||||||
|  |  | ||||||
|         textEditor = await BalloonEditor.create($noteDetailText[0], {}); |         textEditor = await BalloonEditor.create($noteDetailText[0], {}); | ||||||
|  |  | ||||||
|         textEditor.model.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(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     textEditor.setData(noteDetailService.getCurrentNote().content); |     textEditor.setData(noteDetailService.getCurrentNote().content); | ||||||
|   | |||||||
							
								
								
									
										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 | ||||||
|  | } | ||||||
| @@ -39,7 +39,10 @@ function ensureProtectedSession(requireProtectedSession, modal) { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         $dialog.dialog({ |         $dialog.dialog({ | ||||||
|             modal: modal, |             // modal: modal, | ||||||
|  |             // everything is now non-modal, because modal dialog caused weird high CPU usage on opening | ||||||
|  |             // and tearing of text input | ||||||
|  |             modal: false, | ||||||
|             width: 400, |             width: 400, | ||||||
|             open: () => { |             open: () => { | ||||||
|                 if (!modal) { |                 if (!modal) { | ||||||
| @@ -80,11 +83,10 @@ async function setupProtectedSession() { | |||||||
|         $noteDetailWrapper.show(); |         $noteDetailWrapper.show(); | ||||||
|  |  | ||||||
|         protectedSessionDeferred.resolve(); |         protectedSessionDeferred.resolve(); | ||||||
|  |         protectedSessionDeferred = null; | ||||||
|  |  | ||||||
|         $protectedSessionOnButton.addClass('active'); |         $protectedSessionOnButton.addClass('active'); | ||||||
|         $protectedSessionOffButton.removeClass('active'); |         $protectedSessionOffButton.removeClass('active'); | ||||||
|  |  | ||||||
|         protectedSessionDeferred = null; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -105,6 +107,10 @@ async function enterProtectedSessionOnServer(password) { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function protectNoteAndSendToServer() { | async function protectNoteAndSendToServer() { | ||||||
|  |     if (noteDetailService.getCurrentNote().isProtected) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     await ensureProtectedSession(true, true); |     await ensureProtectedSession(true, true); | ||||||
|  |  | ||||||
|     const note = noteDetailService.getCurrentNote(); |     const note = noteDetailService.getCurrentNote(); | ||||||
| @@ -118,6 +124,10 @@ async function protectNoteAndSendToServer() { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function unprotectNoteAndSendToServer() { | async function unprotectNoteAndSendToServer() { | ||||||
|  |     if (!noteDetailService.getCurrentNote().isProtected) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     await ensureProtectedSession(true, true); |     await ensureProtectedSession(true, true); | ||||||
|  |  | ||||||
|     const note = noteDetailService.getCurrentNote(); |     const note = noteDetailService.getCurrentNote(); | ||||||
|   | |||||||
| @@ -1,13 +1,11 @@ | |||||||
| import utils from "./utils.js"; | import utils from "./utils.js"; | ||||||
| import server from "./server.js"; | import optionsInitService from './options_init.js'; | ||||||
|  |  | ||||||
| let lastProtectedSessionOperationDate = null; | let lastProtectedSessionOperationDate = null; | ||||||
| let protectedSessionTimeout = null; | let protectedSessionTimeout = null; | ||||||
| let protectedSessionId = null; | let protectedSessionId = null; | ||||||
|  |  | ||||||
| $(document).ready(() => { | optionsInitService.optionsReady.then(options => protectedSessionTimeout = options.protectedSessionTimeout); | ||||||
|     server.get('options').then(options => protectedSessionTimeout = options.protectedSessionTimeout); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| setInterval(() => { | setInterval(() => { | ||||||
|     if (lastProtectedSessionOperationDate !== null && new Date().getTime() - lastProtectedSessionOperationDate.getTime() > protectedSessionTimeout * 1000) { |     if (lastProtectedSessionOperationDate !== null && new Date().getTime() - lastProtectedSessionOperationDate.getTime() > protectedSessionTimeout * 1000) { | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import treeService from './tree.js'; | import treeService from './tree.js'; | ||||||
| import server from './server.js'; | import server from './server.js'; | ||||||
|  | import treeUtils from "./tree_utils.js"; | ||||||
| 
 | 
 | ||||||
| const $tree = $("#tree"); | const $tree = $("#tree"); | ||||||
| const $searchInput = $("input[name='search-text']"); | const $searchInput = $("input[name='search-text']"); | ||||||
| @@ -7,40 +8,62 @@ const $resetSearchButton = $("#reset-search-button"); | |||||||
| const $doSearchButton = $("#do-search-button"); | const $doSearchButton = $("#do-search-button"); | ||||||
| const $saveSearchButton = $("#save-search-button"); | const $saveSearchButton = $("#save-search-button"); | ||||||
| const $searchBox = $("#search-box"); | const $searchBox = $("#search-box"); | ||||||
|  | const $searchResults = $("#search-results"); | ||||||
|  | const $searchResultsInner = $("#search-results-inner"); | ||||||
|  | const $closeSearchButton = $("#close-search-button"); | ||||||
|  | 
 | ||||||
|  | function showSearch() { | ||||||
|  |     $searchBox.show(); | ||||||
|  |     $searchInput.focus(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function hideSearch() { | ||||||
|  |     resetSearch(); | ||||||
|  | 
 | ||||||
|  |     $searchResults.hide(); | ||||||
|  |     $searchBox.hide(); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| function toggleSearch() { | function toggleSearch() { | ||||||
|     if ($searchBox.is(":hidden")) { |     if ($searchBox.is(":hidden")) { | ||||||
|         $searchBox.show(); |         showSearch(); | ||||||
|         $searchInput.focus(); |  | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|         resetSearch(); |         hideSearch(); | ||||||
| 
 |  | ||||||
|         $searchBox.hide(); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function resetSearch() { | function resetSearch() { | ||||||
|     $searchInput.val(""); |     $searchInput.val(""); | ||||||
| 
 |  | ||||||
|     getTree().clearFilter(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function getTree() { | function getTree() { | ||||||
|     return $tree.fancytree('getTree'); |     return $tree.fancytree('getTree'); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function doSearch() { | async function doSearch(searchText) { | ||||||
|     const searchText = $searchInput.val(); |     if (searchText) { | ||||||
| 
 |         $searchInput.val(searchText); | ||||||
|     const noteIds = await server.get('search/' + encodeURIComponent(searchText)); |     } | ||||||
| 
 |     else { | ||||||
|     for (const noteId of noteIds) { |         searchText = $searchInput.val(); | ||||||
|         await treeService.expandToNote(noteId, {noAnimation: true, noEvents: true}); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Pass a string to perform case insensitive matching
 |     const results = await server.get('search/' + encodeURIComponent(searchText)); | ||||||
|     getTree().filterBranches(node => noteIds.includes(node.data.noteId)); | 
 | ||||||
|  |     $searchResultsInner.empty(); | ||||||
|  |     $searchResults.show(); | ||||||
|  | 
 | ||||||
|  |     for (const result of results) { | ||||||
|  |         const link = $('<a>', { | ||||||
|  |             href: 'javascript:', | ||||||
|  |             text: result.title | ||||||
|  |         }).attr('action', 'note').attr('note-path', result.path); | ||||||
|  | 
 | ||||||
|  |         const $result = $('<li>').append(link); | ||||||
|  | 
 | ||||||
|  |         $searchResultsInner.append($result); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function saveSearch() { | async function saveSearch() { | ||||||
| @@ -71,6 +94,11 @@ $resetSearchButton.click(resetSearch); | |||||||
| 
 | 
 | ||||||
| $saveSearchButton.click(saveSearch); | $saveSearchButton.click(saveSearch); | ||||||
| 
 | 
 | ||||||
|  | $closeSearchButton.click(hideSearch); | ||||||
|  | 
 | ||||||
| export default { | export default { | ||||||
|     toggleSearch |     toggleSearch, | ||||||
|  |     resetSearch, | ||||||
|  |     showSearch, | ||||||
|  |     doSearch | ||||||
| }; | }; | ||||||
| @@ -5,7 +5,7 @@ import infoService from "./info.js"; | |||||||
| function getHeaders() { | function getHeaders() { | ||||||
|     let protectedSessionId = null; |     let protectedSessionId = null; | ||||||
|  |  | ||||||
|     try { // this is because protected session might not be declared in some cases - like when it's included in migration page |     try { // this is because protected session might not be declared in some cases | ||||||
|         protectedSessionId = protectedSessionHolder.getProtectedSessionId(); |         protectedSessionId = protectedSessionHolder.getProtectedSessionId(); | ||||||
|     } |     } | ||||||
|     catch(e) {} |     catch(e) {} | ||||||
|   | |||||||
| @@ -183,7 +183,7 @@ async function getRunPath(notePath) { | |||||||
|     return effectivePath.reverse(); |     return effectivePath.reverse(); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function showParentList(noteId, node) { | async function showPaths(noteId, node) { | ||||||
|     utils.assertArguments(noteId, node); |     utils.assertArguments(noteId, node); | ||||||
|  |  | ||||||
|     const note = await treeCache.getNote(noteId); |     const note = await treeCache.getNote(noteId); | ||||||
| @@ -191,26 +191,21 @@ async function showParentList(noteId, node) { | |||||||
|  |  | ||||||
|     $notePathCount.html(parents.length + " path" + (parents.length > 0 ? "s" : "")); |     $notePathCount.html(parents.length + " path" + (parents.length > 0 ? "s" : "")); | ||||||
|  |  | ||||||
|     if (parents.length <= 1) { |     $notePathList.empty(); | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         //$notePathList.show(); |  | ||||||
|         $notePathList.empty(); |  | ||||||
|  |  | ||||||
|         for (const parentNote of parents) { |     for (const parentNote of parents) { | ||||||
|             const parentNotePath = await getSomeNotePath(parentNote); |         const parentNotePath = await getSomeNotePath(parentNote); | ||||||
|             // this is to avoid having root notes leading '/' |         // this is to avoid having root notes leading '/' | ||||||
|             const notePath = parentNotePath ? (parentNotePath + '/' + noteId) : noteId; |         const notePath = parentNotePath ? (parentNotePath + '/' + noteId) : noteId; | ||||||
|             const title = await treeUtils.getNotePathTitle(notePath); |         const title = await treeUtils.getNotePathTitle(notePath); | ||||||
|  |  | ||||||
|             const item = $("<li/>").append(await linkService.createNoteLink(notePath, title)); |         const item = $("<li/>").append(await linkService.createNoteLink(notePath, title)); | ||||||
|  |  | ||||||
|             if (node.getParent().data.noteId === parentNote.noteId) { |         if (node.getParent().data.noteId === parentNote.noteId) { | ||||||
|                 item.addClass("current"); |             item.addClass("current"); | ||||||
|             } |  | ||||||
|  |  | ||||||
|             $notePathList.append(item); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         $notePathList.append(item); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -323,7 +318,7 @@ function initFancyTree(tree) { | |||||||
|  |  | ||||||
|             noteDetailService.switchToNote(node.noteId); |             noteDetailService.switchToNote(node.noteId); | ||||||
|  |  | ||||||
|             showParentList(node.noteId, data.node); |             showPaths(node.noteId, data.node); | ||||||
|         }, |         }, | ||||||
|         expand: (event, data) => setExpandedToServer(data.node.data.branchId, true), |         expand: (event, data) => setExpandedToServer(data.node.data.branchId, true), | ||||||
|         collapse: (event, data) => setExpandedToServer(data.node.data.branchId, false), |         collapse: (event, data) => setExpandedToServer(data.node.data.branchId, false), | ||||||
|   | |||||||
| @@ -74,14 +74,21 @@ async function prepareRealBranch(parentNote) { | |||||||
|  |  | ||||||
| async function prepareSearchBranch(note) { | async function prepareSearchBranch(note) { | ||||||
|     const fullNote = await noteDetailService.loadNote(note.noteId); |     const fullNote = await noteDetailService.loadNote(note.noteId); | ||||||
|     const noteIds = await server.get('search/' + encodeURIComponent(fullNote.jsonContent.searchString)); |     const results = await server.get('search/' + encodeURIComponent(fullNote.jsonContent.searchString)); | ||||||
|  |  | ||||||
|  |     const noteIds = results.map(res => res.noteId); | ||||||
|  |  | ||||||
|  |     // force to load all the notes at once instead of one by one | ||||||
|  |     await treeCache.getNotes(noteIds); | ||||||
|  |  | ||||||
|  |     for (const result of results) { | ||||||
|  |         const origBranch = await treeCache.getBranch(result.branchId); | ||||||
|  |  | ||||||
|     for (const noteId of noteIds) { |  | ||||||
|         const branch = new Branch(treeCache, { |         const branch = new Branch(treeCache, { | ||||||
|             branchId: "virt" + utils.randomString(10), |             branchId: "virt" + utils.randomString(10), | ||||||
|             noteId: noteId, |             noteId: result.noteId, | ||||||
|             parentNoteId: note.noteId, |             parentNoteId: note.noteId, | ||||||
|             prefix: '', |             prefix: origBranch.prefix, | ||||||
|             virtual: true |             virtual: true | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -52,6 +52,15 @@ async function getNotePathTitle(notePath) { | |||||||
|  |  | ||||||
|     const titlePath = []; |     const titlePath = []; | ||||||
|  |  | ||||||
|  |     if (notePath.startsWith('root/')) { | ||||||
|  |         notePath = notePath.substr(5); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // special case when we want just root's title | ||||||
|  |     if (notePath === 'root') { | ||||||
|  |         return await getNoteTitle(notePath); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     let parentNoteId = 'root'; |     let parentNoteId = 'root'; | ||||||
|  |  | ||||||
|     for (const noteId of notePath.split('/')) { |     for (const noteId of notePath.split('/')) { | ||||||
|   | |||||||
							
								
								
									
										51
									
								
								src/public/javascripts/services/zoom.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,51 @@ | |||||||
|  | import server from "./server.js"; | ||||||
|  | import utils from "./utils.js"; | ||||||
|  | import optionsInitService from "./options_init.js"; | ||||||
|  |  | ||||||
|  | const MIN_ZOOM = 0.5; | ||||||
|  | const MAX_ZOOM = 2.0; | ||||||
|  |  | ||||||
|  | async function decreaseZoomFactor() { | ||||||
|  |     await setZoomFactorAndSave(getCurrentZoom() - 0.1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function increaseZoomFactor() { | ||||||
|  |     await setZoomFactorAndSave(getCurrentZoom() + 0.1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function setZoomFactor(zoomFactor) { | ||||||
|  |     zoomFactor = parseFloat(zoomFactor); | ||||||
|  |  | ||||||
|  |     const webFrame = require('electron').webFrame; | ||||||
|  |     webFrame.setZoomFactor(zoomFactor); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function setZoomFactorAndSave(zoomFactor) { | ||||||
|  |     if (!utils.isElectron()) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (zoomFactor >= MIN_ZOOM && zoomFactor <= MAX_ZOOM) { | ||||||
|  |         setZoomFactor(zoomFactor); | ||||||
|  |  | ||||||
|  |         await server.put('options/zoomFactor/' + zoomFactor); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         console.log(`Zoom factor ${zoomFactor} outside of the range, ignored.`); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function getCurrentZoom() { | ||||||
|  |     return require('electron').webFrame.getZoomFactor(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | if (utils.isElectron()) { | ||||||
|  |     optionsInitService.optionsReady.then(options => setZoomFactor(options.zoomFactor)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |     decreaseZoomFactor, | ||||||
|  |     increaseZoomFactor, | ||||||
|  |     setZoomFactor, | ||||||
|  |     setZoomFactorAndSave | ||||||
|  | } | ||||||
| @@ -20,6 +20,7 @@ | |||||||
|     background-color: #f1f1f1; |     background-color: #f1f1f1; | ||||||
|     display: flex; |     display: flex; | ||||||
|     align-items: center; |     align-items: center; | ||||||
|  |     padding: 4px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-detail-wrapper { | #note-detail-wrapper { | ||||||
| @@ -51,7 +52,7 @@ | |||||||
|     overflow: auto; |     overflow: auto; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-detail-wrapper.protected, #note-detail-wrapper.protected .CodeMirror { | #note-detail-component-wrapper.protected, #note-detail-component-wrapper.protected .CodeMirror { | ||||||
|     background-color: #eee; |     background-color: #eee; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -66,31 +67,31 @@ ul.fancytree-container { | |||||||
|  |  | ||||||
| /* icons from https://feathericons.com */ | /* icons from https://feathericons.com */ | ||||||
| span.fancytree-node > span.fancytree-icon { | span.fancytree-node > span.fancytree-icon { | ||||||
|     background: url("../images/icons/file.png") 0 0; |     background: url("../images/icons/file-16.png") 0 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| span.fancytree-node.fancytree-folder > span.fancytree-icon { | span.fancytree-node.fancytree-folder > span.fancytree-icon { | ||||||
|     background: url("../images/icons/folder.png") 0 0; |     background: url("../images/icons/folder-16.png") 0 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| span.fancytree-node.code > span.fancytree-icon { | span.fancytree-node.code > span.fancytree-icon { | ||||||
|     background: url("../images/icons/code.png") 0 0; |     background: url("../images/icons/code-16.png") 0 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| span.fancytree-node.fancytree-folder.code > span.fancytree-icon { | span.fancytree-node.fancytree-folder.code > span.fancytree-icon { | ||||||
|     background: url("../images/icons/code-folder.png") 0 0; |     background: url("../images/icons/code-folder-16.png") 0 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| span.fancytree-node.file > span.fancytree-icon { | span.fancytree-node.file > span.fancytree-icon { | ||||||
|     background: url("../images/icons/paperclip.png") 0 0; |     background: url("../images/icons/paperclip-16.png") 0 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| span.fancytree-node.render > span.fancytree-icon { | span.fancytree-node.render > span.fancytree-icon { | ||||||
|     background: url("../images/icons/play.png") 0 0; |     background: url("../images/icons/play-16.png") 0 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| span.fancytree-node.search > span.fancytree-icon { | span.fancytree-node.search > span.fancytree-icon { | ||||||
|     background: url("../images/icons/search-small.png") 0 0; |     background: url("../images/icons/search-small-16.png") 0 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| span.fancytree-node.protected > span.fancytree-icon { | span.fancytree-node.protected > span.fancytree-icon { | ||||||
| @@ -106,7 +107,7 @@ span.fancytree-node.fancytree-active-clone:not(.fancytree-active) .fancytree-tit | |||||||
| } | } | ||||||
|  |  | ||||||
| span.fancytree-node.tree-root > span.fancytree-icon { | span.fancytree-node.tree-root > span.fancytree-icon { | ||||||
|     background: url("../images/icons/tree-root.png") 0 0; |     background: url("../images/icons/tree-root-16.png") 0 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* first nesting level has lower left padding to avoid extra left padding. Other levels are not affected */ | /* first nesting level has lower left padding to avoid extra left padding. Other levels are not affected */ | ||||||
| @@ -138,10 +139,6 @@ span.fancytree-active:not(.fancytree-focused) .fancytree-title { | |||||||
|     width: 24px; |     width: 24px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #protect-button, #unprotect-button { |  | ||||||
|     display: none; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .ui-widget-content a:not(.ui-tabs-anchor) { | .ui-widget-content a:not(.ui-tabs-anchor) { | ||||||
|     color: #337ab7 !important; |     color: #337ab7 !important; | ||||||
| } | } | ||||||
| @@ -170,11 +167,27 @@ div.ui-tooltip { | |||||||
|  |  | ||||||
| #tree { | #tree { | ||||||
|     overflow: auto; |     overflow: auto; | ||||||
|     flex-grow: 100; |     flex-grow: 1; | ||||||
|     flex-shrink: 100; |     flex-shrink: 1; | ||||||
|  |     flex-basis: 60%; | ||||||
|     margin-top: 10px; |     margin-top: 10px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #search-results { | ||||||
|  |     padding: 0 5px 5px 15px; | ||||||
|  |     flex-basis: 40%; | ||||||
|  |     flex-grow: 1; | ||||||
|  |     flex-shrink: 1; | ||||||
|  |     margin-top: 10px; | ||||||
|  |     display: none; | ||||||
|  |     overflow: auto; | ||||||
|  |     border-bottom: 2px solid #ddd; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #search-results ul { | ||||||
|  |     padding: 5px 5px 5px 15px; | ||||||
|  | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
| * .electron-in-page-search-window is a class specified to default | * .electron-in-page-search-window is a class specified to default | ||||||
| * <webview> element for search window. | * <webview> element for search window. | ||||||
| @@ -232,7 +245,7 @@ div.ui-tooltip { | |||||||
| } | } | ||||||
|  |  | ||||||
| .suppressed { | .suppressed { | ||||||
|     filter: opacity(7%); |     display: none; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-type .dropdown-menu li:not(.divider) { | #note-type .dropdown-menu li:not(.divider) { | ||||||
| @@ -241,7 +254,7 @@ div.ui-tooltip { | |||||||
| } | } | ||||||
|  |  | ||||||
| .dropdown-menu li:not(.divider):hover, .dropdown-menu li:not(.divider) a:hover { | .dropdown-menu li:not(.divider):hover, .dropdown-menu li:not(.divider) a:hover { | ||||||
|     background-color: #eee !important; |     background-color: #ccc !important; | ||||||
|     cursor: pointer; |     cursor: pointer; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -359,14 +372,52 @@ div.ui-tooltip { | |||||||
|     display: flex; |     display: flex; | ||||||
| } | } | ||||||
|  |  | ||||||
| .btn { | .btn:not(.btn-primary):not(.btn-danger) { | ||||||
|     border-color: #ddd; |     border-color: #bbb; | ||||||
|  |     background-color: #eee; | ||||||
| } | } | ||||||
|  |  | ||||||
| .btn.active { | .btn.active:not(.btn-primary) { | ||||||
|     background-color: #ddd; |     background-color: #ccc; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-path-list .current a { | #note-path-list .current a { | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | button.icon-button { | ||||||
|  |     height: 28px; | ||||||
|  |     width: 28px; | ||||||
|  |     background: no-repeat center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #note-actions { | ||||||
|  |     margin-left: 10px; | ||||||
|  |     margin-right: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #note-actions .dropdown-menu { | ||||||
|  |     width: 15em; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Themes */ | ||||||
|  |  | ||||||
|  | html.theme-black, html.theme-black img, html.theme-black video { | ||||||
|  |     filter: invert(100%) hue-rotate(180deg); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html.theme-black body { | ||||||
|  |     background: black; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html.theme-dark { | ||||||
|  |     filter: invert(90%) hue-rotate(180deg); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html.theme-dark img, html.theme-dark video { | ||||||
|  |     filter: invert(100%) hue-rotate(180deg); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html.theme-dark body { | ||||||
|  |     background: #191819; | ||||||
| } | } | ||||||
| @@ -1,16 +1,16 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| const autocompleteService = require('../../services/autocomplete'); | const noteCacheService = require('../../services/note_cache'); | ||||||
|  |  | ||||||
| async function getAutocomplete(req) { | async function getAutocomplete(req) { | ||||||
|     const query = req.query.query; |     const query = req.query.query; | ||||||
|  |  | ||||||
|     const results = autocompleteService.getResults(query); |     const results = noteCacheService.findNotes(query); | ||||||
|  |  | ||||||
|     return results.map(res => { |     return results.map(res => { | ||||||
|         return { |         return { | ||||||
|             value: res.title + ' (' + res.path + ')', |             value: res.title + ' (' + res.path + ')', | ||||||
|             title: res.title |             label: res.title | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,10 +21,10 @@ async function loginSync(req) { | |||||||
|         return [400, { message: 'Auth request time is out of sync' }]; |         return [400, { message: 'Auth request time is out of sync' }]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const dbVersion = req.body.dbVersion; |     const syncVersion = req.body.syncVersion; | ||||||
|  |  | ||||||
|     if (dbVersion !== appInfo.dbVersion) { |     if (syncVersion !== appInfo.syncVersion) { | ||||||
|         return [400, { message: 'Non-matching db versions, local is version ' + appInfo.dbVersion }]; |         return [400, { message: 'Non-matching sync versions, local is version ' + appInfo.syncVersion }]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const documentSecret = await options.getOption('documentSecret'); |     const documentSecret = await options.getOption('documentSecret'); | ||||||
|   | |||||||
| @@ -1,25 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
|  |  | ||||||
| const optionService = require('../../services/options'); |  | ||||||
| const migrationService = require('../../services/migration'); |  | ||||||
| const appInfo = require('../../services/app_info'); |  | ||||||
|  |  | ||||||
| async function getMigrationInfo() { |  | ||||||
|     return { |  | ||||||
|         dbVersion: parseInt(await optionService.getOption('dbVersion')), |  | ||||||
|         appDbVersion: appInfo.dbVersion |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function executeMigration() { |  | ||||||
|     const migrations = await migrationService.migrate(); |  | ||||||
|  |  | ||||||
|     return { |  | ||||||
|         migrations: migrations |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| module.exports = { |  | ||||||
|     getMigrationInfo, |  | ||||||
|     executeMigration |  | ||||||
| }; |  | ||||||
| @@ -2,9 +2,10 @@ | |||||||
|  |  | ||||||
| const sql = require('../../services/sql'); | const sql = require('../../services/sql'); | ||||||
| const optionService = require('../../services/options'); | const optionService = require('../../services/options'); | ||||||
|  | const log = require('../../services/log'); | ||||||
|  |  | ||||||
| // options allowed to be updated directly in options dialog | // options allowed to be updated directly in options dialog | ||||||
| const ALLOWED_OPTIONS = ['protectedSessionTimeout', 'noteRevisionSnapshotTimeInterval']; | const ALLOWED_OPTIONS = ['protectedSessionTimeout', 'noteRevisionSnapshotTimeInterval', 'zoomFactor', 'theme']; | ||||||
|  |  | ||||||
| async function getOptions() { | async function getOptions() { | ||||||
|     const options = await sql.getMap("SELECT name, value FROM options WHERE name IN (" |     const options = await sql.getMap("SELECT name, value FROM options WHERE name IN (" | ||||||
| @@ -20,6 +21,8 @@ async function updateOption(req) { | |||||||
|         return [400, "not allowed option to set"]; |         return [400, "not allowed option to set"]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     log.info(`Updating option ${name} to ${value}`); | ||||||
|  |  | ||||||
|     await optionService.setOption(name, value); |     await optionService.setOption(name, value); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,12 +1,12 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| const repository = require('../../services/repository'); | const repository = require('../../services/repository'); | ||||||
| const dateUtils = require('../../services/date_utils'); |  | ||||||
| const optionService = require('../../services/options'); | const optionService = require('../../services/options'); | ||||||
| const RecentNote = require('../../entities/recent_note'); | const RecentNote = require('../../entities/recent_note'); | ||||||
|  | const noteCacheService = require('../../services/note_cache'); | ||||||
|  |  | ||||||
| async function getRecentNotes() { | async function getRecentNotes() { | ||||||
|     return await repository.getEntities(` |     const recentNotes = await repository.getEntities(` | ||||||
|       SELECT  |       SELECT  | ||||||
|         recent_notes.*  |         recent_notes.*  | ||||||
|       FROM  |       FROM  | ||||||
| @@ -18,6 +18,12 @@ async function getRecentNotes() { | |||||||
|       ORDER BY  |       ORDER BY  | ||||||
|         dateCreated DESC |         dateCreated DESC | ||||||
|       LIMIT 200`); |       LIMIT 200`); | ||||||
|  |  | ||||||
|  |     for (const rn of recentNotes) { | ||||||
|  |         rn.title = noteCacheService.getNoteTitleForPath(rn.notePath.split('/')); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return recentNotes; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function addRecentNote(req) { | async function addRecentNote(req) { | ||||||
|   | |||||||
| @@ -2,15 +2,69 @@ | |||||||
|  |  | ||||||
| const sql = require('../../services/sql'); | const sql = require('../../services/sql'); | ||||||
| const noteService = require('../../services/notes'); | const noteService = require('../../services/notes'); | ||||||
|  | const noteCacheService = require('../../services/note_cache'); | ||||||
| const parseFilters = require('../../services/parse_filters'); | const parseFilters = require('../../services/parse_filters'); | ||||||
| const buildSearchQuery = require('../../services/build_search_query'); | const buildSearchQuery = require('../../services/build_search_query'); | ||||||
|  |  | ||||||
| async function searchNotes(req) { | async function searchNotes(req) { | ||||||
|     const {labelFilters, searchText} = parseFilters(req.params.searchString); |     const {labelFilters, searchText} = parseFilters(req.params.searchString); | ||||||
|  |  | ||||||
|     const {query, params} = buildSearchQuery(labelFilters, searchText); |     let labelFiltersNoteIds = null; | ||||||
|  |  | ||||||
|     const noteIds = await sql.getColumn(query, params); |     if (labelFilters.length > 0) { | ||||||
|  |         const {query, params} = buildSearchQuery(labelFilters, searchText); | ||||||
|  |  | ||||||
|  |         labelFiltersNoteIds = await sql.getColumn(query, params); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let searchTextResults = null; | ||||||
|  |  | ||||||
|  |     if (searchText.trim().length > 0) { | ||||||
|  |         searchTextResults = noteCacheService.findNotes(searchText); | ||||||
|  |  | ||||||
|  |         let fullTextNoteIds = await getFullTextResults(searchText); | ||||||
|  |  | ||||||
|  |         for (const noteId of fullTextNoteIds) { | ||||||
|  |             if (!searchTextResults.some(item => item.noteId === noteId)) { | ||||||
|  |                 const result = noteCacheService.getNotePath(noteId); | ||||||
|  |  | ||||||
|  |                 if (result) { | ||||||
|  |                     searchTextResults.push(result); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let results; | ||||||
|  |  | ||||||
|  |     if (labelFiltersNoteIds && searchTextResults) { | ||||||
|  |         results = labelFiltersNoteIds.filter(item => searchTextResults.includes(item.noteId)); | ||||||
|  |     } | ||||||
|  |     else if (labelFiltersNoteIds) { | ||||||
|  |         results = labelFiltersNoteIds.map(noteCacheService.getNotePath).filter(res => !!res); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         results = searchTextResults; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return results; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function getFullTextResults(searchText) { | ||||||
|  |     const tokens = searchText.toLowerCase().split(" "); | ||||||
|  |     const tokenSql = ["1=1"]; | ||||||
|  |  | ||||||
|  |     for (const token of tokens) { | ||||||
|  |         // FIXME: escape token! | ||||||
|  |         tokenSql.push(`(title LIKE '%${token}%' OR content LIKE '%${token}%')`); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const noteIds = await sql.getColumn(` | ||||||
|  |       SELECT DISTINCT noteId  | ||||||
|  |       FROM notes  | ||||||
|  |       WHERE isDeleted = 0  | ||||||
|  |         AND isProtected = 0 | ||||||
|  |         AND ${tokenSql.join(' AND ')}`); | ||||||
|  |  | ||||||
|     return noteIds; |     return noteIds; | ||||||
| } | } | ||||||
| @@ -20,7 +74,7 @@ async function saveSearchToNote(req) { | |||||||
|         searchString: req.params.searchString |         searchString: req.params.searchString | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     const {note} = await noteService.createNote('root', 'Search note', noteContent, { |     const {note} = await noteService.createNote('root', req.params.searchString, noteContent, { | ||||||
|         json: true, |         json: true, | ||||||
|         type: 'search', |         type: 'search', | ||||||
|         mime: "application/json" |         mime: "application/json" | ||||||
|   | |||||||
| @@ -4,9 +4,11 @@ const sourceIdService = require('../services/source_id'); | |||||||
| const sql = require('../services/sql'); | const sql = require('../services/sql'); | ||||||
| const labelService = require('../services/labels'); | const labelService = require('../services/labels'); | ||||||
| const config = require('../services/config'); | const config = require('../services/config'); | ||||||
|  | const optionService = require('../services/options'); | ||||||
|  |  | ||||||
| async function index(req, res) { | async function index(req, res) { | ||||||
|     res.render('index', { |     res.render('index', { | ||||||
|  |         theme: await optionService.getOption('theme'), | ||||||
|         sourceId: await sourceIdService.generateSourceId(), |         sourceId: await sourceIdService.generateSourceId(), | ||||||
|         maxSyncIdAtLoad: await sql.getValue("SELECT MAX(id) FROM sync"), |         maxSyncIdAtLoad: await sql.getValue("SELECT MAX(id) FROM sync"), | ||||||
|         instanceName: config.General ? config.General.instanceName : null, |         instanceName: config.General ? config.General.instanceName : null, | ||||||
|   | |||||||
| @@ -1,9 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
|  |  | ||||||
| function migrationPage(req, res) { |  | ||||||
|     res.render('migration', {}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| module.exports = { |  | ||||||
|     migrationPage |  | ||||||
| }; |  | ||||||
| @@ -1,6 +1,5 @@ | |||||||
| const indexRoute = require('./index'); | const indexRoute = require('./index'); | ||||||
| const loginRoute = require('./login'); | const loginRoute = require('./login'); | ||||||
| const migrationRoute = require('./migration'); |  | ||||||
| const setupRoute = require('./setup'); | const setupRoute = require('./setup'); | ||||||
| const multer = require('multer')(); | const multer = require('multer')(); | ||||||
|  |  | ||||||
| @@ -14,7 +13,6 @@ const noteRevisionsApiRoute = require('./api/note_revisions'); | |||||||
| const recentChangesApiRoute = require('./api/recent_changes'); | const recentChangesApiRoute = require('./api/recent_changes'); | ||||||
| const optionsApiRoute = require('./api/options'); | const optionsApiRoute = require('./api/options'); | ||||||
| const passwordApiRoute = require('./api/password'); | const passwordApiRoute = require('./api/password'); | ||||||
| const migrationApiRoute = require('./api/migration'); |  | ||||||
| const syncApiRoute = require('./api/sync'); | const syncApiRoute = require('./api/sync'); | ||||||
| const loginApiRoute = require('./api/login'); | const loginApiRoute = require('./api/login'); | ||||||
| const eventLogRoute = require('./api/event_log'); | const eventLogRoute = require('./api/event_log'); | ||||||
| @@ -96,7 +94,6 @@ function register(app) { | |||||||
|     route(GET, '/login', [], loginRoute.loginPage); |     route(GET, '/login', [], loginRoute.loginPage); | ||||||
|     route(POST, '/login', [], loginRoute.login); |     route(POST, '/login', [], loginRoute.login); | ||||||
|     route(POST, '/logout', [auth.checkAuth], loginRoute.logout); |     route(POST, '/logout', [auth.checkAuth], loginRoute.logout); | ||||||
|     route(GET, '/migration', [auth.checkAuthForMigrationPage], migrationRoute.migrationPage); |  | ||||||
|     route(GET, '/setup', [auth.checkAppNotInitialized], setupRoute.setupPage); |     route(GET, '/setup', [auth.checkAppNotInitialized], setupRoute.setupPage); | ||||||
|  |  | ||||||
|     apiRoute(GET, '/api/tree', treeApiRoute.getTree); |     apiRoute(GET, '/api/tree', treeApiRoute.getTree); | ||||||
| @@ -180,9 +177,6 @@ function register(app) { | |||||||
|     apiRoute(GET, '/api/search/:searchString', searchRoute.searchNotes); |     apiRoute(GET, '/api/search/:searchString', searchRoute.searchNotes); | ||||||
|     apiRoute(POST, '/api/search/:searchString', searchRoute.saveSearchToNote); |     apiRoute(POST, '/api/search/:searchString', searchRoute.saveSearchToNote); | ||||||
|  |  | ||||||
|     route(GET, '/api/migration', [auth.checkApiAuthForMigrationPage], migrationApiRoute.getMigrationInfo, apiResultHandler); |  | ||||||
|     route(POST, '/api/migration', [auth.checkApiAuthForMigrationPage], migrationApiRoute.executeMigration, apiResultHandler); |  | ||||||
|  |  | ||||||
|     route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler); |     route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler); | ||||||
|     // this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username) |     // this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username) | ||||||
|     apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession); |     apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession); | ||||||
|   | |||||||
| @@ -3,11 +3,13 @@ | |||||||
| const build = require('./build'); | const build = require('./build'); | ||||||
| const packageJson = require('../../package'); | const packageJson = require('../../package'); | ||||||
|  |  | ||||||
| const APP_DB_VERSION = 96; | const APP_DB_VERSION = 100; | ||||||
|  | const SYNC_VERSION = 1; | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     appVersion: packageJson.version, |     appVersion: packageJson.version, | ||||||
|     dbVersion: APP_DB_VERSION, |     dbVersion: APP_DB_VERSION, | ||||||
|  |     syncVersion: SYNC_VERSION, | ||||||
|     buildDate: build.buildDate, |     buildDate: build.buildDate, | ||||||
|     buildRevision: build.buildRevision |     buildRevision: build.buildRevision | ||||||
| }; | }; | ||||||
| @@ -12,18 +12,6 @@ async function checkAuth(req, res, next) { | |||||||
|     else if (!req.session.loggedIn && !utils.isElectron()) { |     else if (!req.session.loggedIn && !utils.isElectron()) { | ||||||
|         res.redirect("login"); |         res.redirect("login"); | ||||||
|     } |     } | ||||||
|     else if (!await sqlInit.isDbUpToDate()) { |  | ||||||
|         res.redirect("migration"); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         next(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function checkAuthForMigrationPage(req, res, next) { |  | ||||||
|     if (!req.session.loggedIn && !utils.isElectron()) { |  | ||||||
|         res.redirect("login"); |  | ||||||
|     } |  | ||||||
|     else { |     else { | ||||||
|         next(); |         next(); | ||||||
|     } |     } | ||||||
| @@ -35,27 +23,12 @@ async function checkApiAuthOrElectron(req, res, next) { | |||||||
|     if (!req.session.loggedIn && !utils.isElectron()) { |     if (!req.session.loggedIn && !utils.isElectron()) { | ||||||
|         res.status(401).send("Not authorized"); |         res.status(401).send("Not authorized"); | ||||||
|     } |     } | ||||||
|     else if (await sqlInit.isDbUpToDate()) { |  | ||||||
|         next(); |  | ||||||
|     } |  | ||||||
|     else { |     else { | ||||||
|         res.status(409).send("Mismatched app versions"); // need better response than that |         next(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function checkApiAuth(req, res, next) { | async function checkApiAuth(req, res, next) { | ||||||
|     if (!req.session.loggedIn) { |  | ||||||
|         res.status(401).send("Not authorized"); |  | ||||||
|     } |  | ||||||
|     else if (await sqlInit.isDbUpToDate()) { |  | ||||||
|         next(); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         res.status(409).send("Mismatched app versions"); // need better response than that |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function checkApiAuthForMigrationPage(req, res, next) { |  | ||||||
|     if (!req.session.loggedIn) { |     if (!req.session.loggedIn) { | ||||||
|         res.status(401).send("Not authorized"); |         res.status(401).send("Not authorized"); | ||||||
|     } |     } | ||||||
| @@ -79,19 +52,14 @@ async function checkSenderToken(req, res, next) { | |||||||
|     if (await sql.getValue("SELECT COUNT(*) FROM api_tokens WHERE isDeleted = 0 AND token = ?", [token]) === 0) { |     if (await sql.getValue("SELECT COUNT(*) FROM api_tokens WHERE isDeleted = 0 AND token = ?", [token]) === 0) { | ||||||
|         res.status(401).send("Not authorized"); |         res.status(401).send("Not authorized"); | ||||||
|     } |     } | ||||||
|     else if (await sqlInit.isDbUpToDate()) { |  | ||||||
|         next(); |  | ||||||
|     } |  | ||||||
|     else { |     else { | ||||||
|         res.status(409).send("Mismatched app versions"); // need better response than that |         next(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     checkAuth, |     checkAuth, | ||||||
|     checkAuthForMigrationPage, |  | ||||||
|     checkApiAuth, |     checkApiAuth, | ||||||
|     checkApiAuthForMigrationPage, |  | ||||||
|     checkAppNotInitialized, |     checkAppNotInitialized, | ||||||
|     checkApiAuthOrElectron, |     checkApiAuthOrElectron, | ||||||
|     checkSenderToken |     checkSenderToken | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| module.exports = { buildDate:"2018-05-31T23:23:44-04:00", buildRevision: "80d2457b23b916b869fe2a91ae4e65e33ab42549" }; | module.exports = { buildDate:"2018-06-16T13:34:39-04:00", buildRevision: "3a95c9e1bc4d608a2135c9412732c2c7fbb2834c" }; | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| module.exports = function(labelFilters, searchText) { | module.exports = function(labelFilters) { | ||||||
|     const joins = []; |     const joins = []; | ||||||
|     const joinParams = []; |     const joinParams = []; | ||||||
|     let where = '1'; |     let where = '1'; | ||||||
| @@ -44,16 +44,6 @@ module.exports = function(labelFilters, searchText) { | |||||||
|     let searchCondition = ''; |     let searchCondition = ''; | ||||||
|     const searchParams = []; |     const searchParams = []; | ||||||
|  |  | ||||||
|     if (searchText.trim() !== '') { |  | ||||||
|         // searching in protected notes is pointless because of encryption |  | ||||||
|         searchCondition = ' AND (notes.isProtected = 0 AND (notes.title LIKE ? OR notes.content LIKE ?))'; |  | ||||||
|  |  | ||||||
|         searchText = '%' + searchText.trim() + '%'; |  | ||||||
|  |  | ||||||
|         searchParams.push(searchText); |  | ||||||
|         searchParams.push(searchText); // two occurences in searchCondition |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const query = `SELECT DISTINCT notes.noteId FROM notes |     const query = `SELECT DISTINCT notes.noteId FROM notes | ||||||
|             ${joins.join('\r\n')} |             ${joins.join('\r\n')} | ||||||
|               WHERE  |               WHERE  | ||||||
|   | |||||||
| @@ -16,8 +16,11 @@ const RecentNote = require('../entities/recent_note'); | |||||||
| const Option = require('../entities/option'); | const Option = require('../entities/option'); | ||||||
|  |  | ||||||
| async function getHash(entityConstructor, whereBranch) { | async function getHash(entityConstructor, whereBranch) { | ||||||
|     let contentToHash = await sql.getValue(`SELECT GROUP_CONCAT(hash) FROM ${entityConstructor.tableName} ` |     // subselect is necessary to have correct ordering in GROUP_CONCAT | ||||||
|                 + (whereBranch ? `WHERE ${whereBranch} ` : '') + `ORDER BY ${entityConstructor.primaryKeyName}`); |     const query = `SELECT GROUP_CONCAT(hash) FROM (SELECT hash FROM ${entityConstructor.tableName} ` | ||||||
|  |         + (whereBranch ? `WHERE ${whereBranch} ` : '') + `ORDER BY ${entityConstructor.primaryKeyName})`; | ||||||
|  |  | ||||||
|  |     let contentToHash = await sql.getValue(query); | ||||||
|  |  | ||||||
|     if (!contentToHash) { // might be null in case of no rows |     if (!contentToHash) { // might be null in case of no rows | ||||||
|         contentToHash = ""; |         contentToHash = ""; | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ const Label = require('../entities/label'); | |||||||
| const BUILTIN_LABELS = [ | const BUILTIN_LABELS = [ | ||||||
|     'disableVersioning', |     'disableVersioning', | ||||||
|     'calendarRoot', |     'calendarRoot', | ||||||
|     'hideInAutocomplete', |     'archived', | ||||||
|     'excludeFromExport', |     'excludeFromExport', | ||||||
|     'run', |     'run', | ||||||
|     'manualTransactionHandling', |     'manualTransactionHandling', | ||||||
|   | |||||||
| @@ -8,8 +8,9 @@ const utils = require('./utils'); | |||||||
| let noteTitles; | let noteTitles; | ||||||
| let protectedNoteTitles; | let protectedNoteTitles; | ||||||
| let noteIds; | let noteIds; | ||||||
|  | let childParentToBranchId = {}; | ||||||
| const childToParent = {}; | const childToParent = {}; | ||||||
| const hideInAutocomplete = {}; | const archived = {}; | ||||||
| 
 | 
 | ||||||
| // key is 'childNoteId-parentNoteId' as a replacement for branchId which we don't use here
 | // key is 'childNoteId-parentNoteId' as a replacement for branchId which we don't use here
 | ||||||
| let prefixes = {}; | let prefixes = {}; | ||||||
| @@ -20,21 +21,22 @@ async function load() { | |||||||
| 
 | 
 | ||||||
|     prefixes = await sql.getMap(`SELECT noteId || '-' || parentNoteId, prefix FROM branches WHERE prefix IS NOT NULL AND prefix != ''`); |     prefixes = await sql.getMap(`SELECT noteId || '-' || parentNoteId, prefix FROM branches WHERE prefix IS NOT NULL AND prefix != ''`); | ||||||
| 
 | 
 | ||||||
|     const relations = await sql.getRows(`SELECT noteId, parentNoteId FROM branches WHERE isDeleted = 0`); |     const relations = await sql.getRows(`SELECT branchId, noteId, parentNoteId FROM branches WHERE isDeleted = 0`); | ||||||
| 
 | 
 | ||||||
|     for (const rel of relations) { |     for (const rel of relations) { | ||||||
|         childToParent[rel.noteId] = childToParent[rel.noteId] || []; |         childToParent[rel.noteId] = childToParent[rel.noteId] || []; | ||||||
|         childToParent[rel.noteId].push(rel.parentNoteId); |         childToParent[rel.noteId].push(rel.parentNoteId); | ||||||
|  |         childParentToBranchId[`${rel.noteId}-${rel.parentNoteId}`] = rel.branchId; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const hiddenLabels = await sql.getColumn(`SELECT noteId FROM labels WHERE isDeleted = 0 AND name = 'hideInAutocomplete'`); |     const hiddenLabels = await sql.getColumn(`SELECT noteId FROM labels WHERE isDeleted = 0 AND name = 'archived'`); | ||||||
| 
 | 
 | ||||||
|     for (const noteId of hiddenLabels) { |     for (const noteId of hiddenLabels) { | ||||||
|         hideInAutocomplete[noteId] = true; |         archived[noteId] = true; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function getResults(query) { | function findNotes(query) { | ||||||
|     if (!noteTitles || query.length <= 2) { |     if (!noteTitles || query.length <= 2) { | ||||||
|         return []; |         return []; | ||||||
|     } |     } | ||||||
| @@ -49,7 +51,7 @@ function getResults(query) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for (const noteId of noteIds) { |     for (const noteId of noteIds) { | ||||||
|         if (hideInAutocomplete[noteId]) { |         if (archived[noteId]) { | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -59,7 +61,7 @@ function getResults(query) { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         for (const parentNoteId of parents) { |         for (const parentNoteId of parents) { | ||||||
|             if (hideInAutocomplete[parentNoteId]) { |             if (archived[parentNoteId]) { | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| @@ -91,8 +93,12 @@ function search(noteId, tokens, path, results) { | |||||||
| 
 | 
 | ||||||
|         if (retPath) { |         if (retPath) { | ||||||
|             const noteTitle = getNoteTitleForPath(retPath); |             const noteTitle = getNoteTitleForPath(retPath); | ||||||
|  |             const thisNoteId = retPath[retPath.length - 1]; | ||||||
|  |             const thisParentNoteId = retPath[retPath.length - 2]; | ||||||
| 
 | 
 | ||||||
|             results.push({ |             results.push({ | ||||||
|  |                 noteId: thisNoteId, | ||||||
|  |                 branchId: childParentToBranchId[`${thisNoteId}-${thisParentNoteId}`], | ||||||
|                 title: noteTitle, |                 title: noteTitle, | ||||||
|                 path: retPath.join('/') |                 path: retPath.join('/') | ||||||
|             }); |             }); | ||||||
| @@ -111,7 +117,7 @@ function search(noteId, tokens, path, results) { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (parentNoteId === 'root' || hideInAutocomplete[parentNoteId]) { |         if (parentNoteId === 'root' || archived[parentNoteId]) { | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -155,6 +161,15 @@ function getNoteTitle(noteId, parentNoteId) { | |||||||
| function getNoteTitleForPath(path) { | function getNoteTitleForPath(path) { | ||||||
|     const titles = []; |     const titles = []; | ||||||
| 
 | 
 | ||||||
|  |     if (path[0] === 'root') { | ||||||
|  |         if (path.length === 1) { | ||||||
|  |             return getNoteTitle('root'); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             path = path.slice(1); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     let parentNoteId = 'root'; |     let parentNoteId = 'root'; | ||||||
| 
 | 
 | ||||||
|     for (const noteId of path) { |     for (const noteId of path) { | ||||||
| @@ -180,6 +195,10 @@ function getSomePath(noteId, path) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for (const parentNoteId of parents) { |     for (const parentNoteId of parents) { | ||||||
|  |         if (archived[parentNoteId]) { | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         const retPath = getSomePath(parentNoteId, path.concat([noteId])); |         const retPath = getSomePath(parentNoteId, path.concat([noteId])); | ||||||
| 
 | 
 | ||||||
|         if (retPath) { |         if (retPath) { | ||||||
| @@ -190,6 +209,22 @@ function getSomePath(noteId, path) { | |||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function getNotePath(noteId) { | ||||||
|  |     const retPath = getSomePath(noteId, []); | ||||||
|  | 
 | ||||||
|  |     if (retPath) { | ||||||
|  |         const noteTitle = getNoteTitleForPath(retPath); | ||||||
|  |         const parentNoteId = childToParent[noteId][0]; | ||||||
|  | 
 | ||||||
|  |         return { | ||||||
|  |             noteId: noteId, | ||||||
|  |             branchId: childParentToBranchId[`${noteId}-${parentNoteId}`], | ||||||
|  |             title: noteTitle, | ||||||
|  |             path: retPath.join('/') | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entityId}) => { | eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entityId}) => { | ||||||
|     if (entityName === 'notes') { |     if (entityName === 'notes') { | ||||||
|         const note = await repository.getNote(entityId); |         const note = await repository.getNote(entityId); | ||||||
| @@ -211,6 +246,7 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entityId | |||||||
| 
 | 
 | ||||||
|         if (branch.isDeleted) { |         if (branch.isDeleted) { | ||||||
|             delete prefixes[branch.noteId + '-' + branch.parentNoteId]; |             delete prefixes[branch.noteId + '-' + branch.parentNoteId]; | ||||||
|  |             delete childParentToBranchId[branch.noteId + '-' + branch.parentNoteId]; | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             if (branch.prefix) { |             if (branch.prefix) { | ||||||
| @@ -219,21 +255,22 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entityId | |||||||
| 
 | 
 | ||||||
|             childToParent[branch.noteId] = childToParent[branch.noteId] || []; |             childToParent[branch.noteId] = childToParent[branch.noteId] || []; | ||||||
|             childToParent[branch.noteId].push(branch.parentNoteId); |             childToParent[branch.noteId].push(branch.parentNoteId); | ||||||
|  |             childParentToBranchId[branch.noteId + '-' + branch.parentNoteId] = branch.branchId; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     else if (entityName === 'labels') { |     else if (entityName === 'labels') { | ||||||
|         const label = await repository.getLabel(entityId); |         const label = await repository.getLabel(entityId); | ||||||
| 
 | 
 | ||||||
|         if (label.name === 'hideInAutocomplete') { |         if (label.name === 'archived') { | ||||||
|             // we're not using label object directly, since there might be other non-deleted hideInAutocomplete label
 |             // we're not using label object directly, since there might be other non-deleted archived label
 | ||||||
|             const hideLabel = await repository.getEntity(`SELECT * FROM labels WHERE isDeleted = 0 
 |             const hideLabel = await repository.getEntity(`SELECT * FROM labels WHERE isDeleted = 0 
 | ||||||
|                                  AND name = 'hideInAutocomplete' AND noteId = ?`, [label.noteId]);
 |                                  AND name = 'archived' AND noteId = ?`, [label.noteId]);
 | ||||||
| 
 | 
 | ||||||
|             if (hideLabel) { |             if (hideLabel) { | ||||||
|                 hideInAutocomplete[label.noteId] = true; |                 archived[label.noteId] = true; | ||||||
|             } |             } | ||||||
|             else { |             else { | ||||||
|                 delete hideInAutocomplete[label.noteId]; |                 delete archived[label.noteId]; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -250,5 +287,7 @@ eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, async () => { | |||||||
| sqlInit.dbReady.then(() => utils.stopWatch("Autocomplete load", load)); | sqlInit.dbReady.then(() => utils.stopWatch("Autocomplete load", load)); | ||||||
| 
 | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|     getResults |     findNotes, | ||||||
|  |     getNotePath, | ||||||
|  |     getNoteTitleForPath | ||||||
| }; | }; | ||||||
| @@ -2,7 +2,6 @@ const repository = require('./repository'); | |||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
| const dateUtils = require('./date_utils'); | const dateUtils = require('./date_utils'); | ||||||
| const appInfo = require('./app_info'); | const appInfo = require('./app_info'); | ||||||
| const Option = require('../entities/option'); |  | ||||||
|  |  | ||||||
| async function getOption(name) { | async function getOption(name) { | ||||||
|     const option = await repository.getOption(name); |     const option = await repository.getOption(name); | ||||||
| @@ -27,6 +26,9 @@ async function setOption(name, value) { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function createOption(name, value, isSynced) { | async function createOption(name, value, isSynced) { | ||||||
|  |     // to avoid circular dependency, need to find better solution | ||||||
|  |     const Option = require('../entities/option'); | ||||||
|  |  | ||||||
|     await new Option({ |     await new Option({ | ||||||
|         name: name, |         name: name, | ||||||
|         value: value, |         value: value, | ||||||
| @@ -53,6 +55,9 @@ async function initOptions(startNotePath) { | |||||||
|  |  | ||||||
|     await createOption('lastSyncedPull', appInfo.dbVersion, false); |     await createOption('lastSyncedPull', appInfo.dbVersion, false); | ||||||
|     await createOption('lastSyncedPush', 0, false); |     await createOption('lastSyncedPush', 0, false); | ||||||
|  |  | ||||||
|  |     await createOption('zoomFactor', 1.0, false); | ||||||
|  |     await createOption('theme', 'white', false); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   | |||||||
| @@ -42,7 +42,10 @@ const dbReady = new Promise((resolve, reject) => { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (!await isDbUpToDate()) { |         if (!await isDbUpToDate()) { | ||||||
|             return; |             // avoiding circular dependency | ||||||
|  |             const migrationService = require('./migration'); | ||||||
|  |  | ||||||
|  |             await migrationService.migrate(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         resolve(db); |         resolve(db); | ||||||
| @@ -94,6 +97,12 @@ async function isDbUpToDate() { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function isUserInitialized() { | async function isUserInitialized() { | ||||||
|  |     const optionsTable = await sql.getRows("SELECT name FROM sqlite_master WHERE type='table' AND name='options'"); | ||||||
|  |  | ||||||
|  |     if (optionsTable.length !== 1) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     const username = await sql.getValue("SELECT value FROM options WHERE name = 'username'"); |     const username = await sql.getValue("SELECT value FROM options WHERE name = 'username'"); | ||||||
|  |  | ||||||
|     return !!username; |     return !!username; | ||||||
|   | |||||||
| @@ -22,13 +22,6 @@ let syncServerCertificate = null; | |||||||
| async function sync() { | async function sync() { | ||||||
|     try { |     try { | ||||||
|         await syncMutexService.doExclusively(async () => { |         await syncMutexService.doExclusively(async () => { | ||||||
|             if (!await sqlInit.isDbUpToDate()) { |  | ||||||
|                 return { |  | ||||||
|                     success: false, |  | ||||||
|                     message: "DB not up to date" |  | ||||||
|                 }; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             const syncContext = await login(); |             const syncContext = await login(); | ||||||
|  |  | ||||||
|             await pushSync(syncContext); |             await pushSync(syncContext); | ||||||
| @@ -76,7 +69,7 @@ async function login() { | |||||||
|  |  | ||||||
|     const resp = await syncRequest(syncContext, 'POST', '/api/login/sync', { |     const resp = await syncRequest(syncContext, 'POST', '/api/login/sync', { | ||||||
|         timestamp: timestamp, |         timestamp: timestamp, | ||||||
|         dbVersion: appInfo.dbVersion, |         syncVersion: appInfo.syncVersion, | ||||||
|         hash: hash |         hash: hash | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| @@ -212,7 +205,7 @@ const primaryKeys = { | |||||||
|     "note_images": "noteImageId", |     "note_images": "noteImageId", | ||||||
|     "labels": "labelId", |     "labels": "labelId", | ||||||
|     "api_tokens": "apiTokenId", |     "api_tokens": "apiTokenId", | ||||||
|     "options": "optionId" |     "options": "name" | ||||||
| }; | }; | ||||||
|  |  | ||||||
| async function getEntityRow(entityName, entityId) { | async function getEntityRow(entityName, entityId) { | ||||||
|   | |||||||
| @@ -79,6 +79,32 @@ function stripTags(text) { | |||||||
|     return text.replace(/<(?:.|\n)*?>/gm, ''); |     return text.replace(/<(?:.|\n)*?>/gm, ''); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function intersection(a, b) { | ||||||
|  |     return a.filter(value => b.indexOf(value) !== -1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function union(a, b) { | ||||||
|  |     const obj = {}; | ||||||
|  |  | ||||||
|  |     for (let i = a.length-1; i >= 0; i--) { | ||||||
|  |         obj[a[i]] = a[i]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     for (let i = b.length-1; i >= 0; i--) { | ||||||
|  |         obj[b[i]] = b[i]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const res = []; | ||||||
|  |  | ||||||
|  |     for (const k in obj) { | ||||||
|  |         if (obj.hasOwnProperty(k)) { // <-- optional | ||||||
|  |             res.push(obj[k]); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return res; | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     randomSecureToken, |     randomSecureToken, | ||||||
|     randomString, |     randomString, | ||||||
| @@ -93,5 +119,7 @@ module.exports = { | |||||||
|     stopWatch, |     stopWatch, | ||||||
|     unescapeHtml, |     unescapeHtml, | ||||||
|     toObject, |     toObject, | ||||||
|     stripTags |     stripTags, | ||||||
|  |     intersection, | ||||||
|  |     union | ||||||
| }; | }; | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||||
| <html lang="en"> | <html lang="en" class="theme-<%= theme %>"> | ||||||
|   <head> |   <head> | ||||||
|     <meta charset="utf-8"> |     <meta charset="utf-8"> | ||||||
|     <title>Trilium Notes</title> |     <title>Trilium Notes</title> | ||||||
| @@ -7,24 +7,18 @@ | |||||||
|   <body> |   <body> | ||||||
|     <div id="container" style="display:none;"> |     <div id="container" style="display:none;"> | ||||||
|       <div id="header" class="hide-toggle"> |       <div id="header" class="hide-toggle"> | ||||||
|         <div id="header-title"> |  | ||||||
|           <img src="/images/app-icons/png/24x24.png"> |  | ||||||
|  |  | ||||||
|           Trilium Notes |  | ||||||
|         </div> |  | ||||||
|  |  | ||||||
|         <div id="history-navigation" style="display: none;"> |         <div id="history-navigation" style="display: none;"> | ||||||
|           <a id="history-back-button" title="Go to previous note." class="icon-action" |           <a id="history-back-button" title="Go to previous note." class="icon-action" | ||||||
|              style="background: url('/images/icons/back.png')"></a> |              style="background: url('/images/icons/back-24.png')"></a> | ||||||
|  |  | ||||||
|               |               | ||||||
|  |  | ||||||
|           <a id="history-forward-button" title="Go to next note." class="icon-action" |           <a id="history-forward-button" title="Go to next note." class="icon-action" | ||||||
|              style="background: url('/images/icons/forward.png')"></a> |              style="background: url('/images/icons/forward-24.png')"></a> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|         <div style="flex-grow: 100; display: flex;"> |         <div style="flex-grow: 100; display: flex;"> | ||||||
|           <button class="btn btn-xs" id="jump-to-note-button" title="CTRL+J">Jump to note</button> |           <button class="btn btn-xs" id="jump-to-note-dialog-button" title="CTRL+J">Jump to note</button> | ||||||
|           <button class="btn btn-xs" id="recent-notes-button" title="CTRL+E">Recent notes</button> |           <button class="btn btn-xs" id="recent-notes-button" title="CTRL+E">Recent notes</button> | ||||||
|           <button class="btn btn-xs" id="recent-changes-button">Recent changes</button> |           <button class="btn btn-xs" id="recent-changes-button">Recent changes</button> | ||||||
|           <div> |           <div> | ||||||
| @@ -57,18 +51,18 @@ | |||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       <div style="grid-area: left-pane; display: flex; flex-direction: column;" class="hide-toggle"> |       <div style="grid-area: left-pane; display: flex; flex-direction: column;" class="hide-toggle"> | ||||||
|         <div style="display: flex; justify-content: space-around; padding: 10px 0 10px 0; margin: 0 20px 0 20px; border: 1px solid #ccc;"> |         <div style="display: flex; justify-content: space-around; padding: 10px 0 10px 0; margin: 0 10px 0 16px; border: 1px solid #ccc;"> | ||||||
|           <a id="create-top-level-note-button" title="Create new top level note" class="icon-action" |           <a id="create-top-level-note-button" title="Create new top level note" class="icon-action" | ||||||
|              style="background: url('/images/icons/file-plus.png')"></a> |              style="background: url('/images/icons/file-plus-24.png')"></a> | ||||||
|  |  | ||||||
|           <a id="collapse-tree-button" title="Collapse note tree" class="icon-action" |           <a id="collapse-tree-button" title="Collapse note tree" class="icon-action" | ||||||
|              style="background: url('/images/icons/list.png')"></a> |              style="background: url('/images/icons/list-24.png')"></a> | ||||||
|  |  | ||||||
|           <a id="scroll-to-current-note-button" title="Scroll to current note. Shortcut CTRL+." class="icon-action" |           <a id="scroll-to-current-note-button" title="Scroll to current note. Shortcut CTRL+." class="icon-action" | ||||||
|              style="background: url('/images/icons/crosshair.png')"></a> |              style="background: url('/images/icons/crosshair-24.png')"></a> | ||||||
|  |  | ||||||
|           <a id="toggle-search-button" title="Search in notes" class="icon-action" |           <a id="toggle-search-button" title="Search in notes" class="icon-action" | ||||||
|              style="background: url('/images/icons/search.png')"></a> |              style="background: url('/images/icons/search-24.png')"></a> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|         <input type="file" id="import-upload" style="display: none" /> |         <input type="file" id="import-upload" style="display: none" /> | ||||||
| @@ -76,22 +70,37 @@ | |||||||
|         <div id="search-box" style="display: none; padding: 10px; margin-top: 10px;"> |         <div id="search-box" style="display: none; padding: 10px; margin-top: 10px;"> | ||||||
|           <div style="display: flex; align-items: center;"> |           <div style="display: flex; align-items: center;"> | ||||||
|             <input name="search-text" placeholder="Search text, labels" style="flex-grow: 100; margin-left: 5px; margin-right: 5px;" autocomplete="off"> |             <input name="search-text" placeholder="Search text, labels" style="flex-grow: 100; margin-left: 5px; margin-right: 5px;" autocomplete="off"> | ||||||
|             <button id="do-search-button" class="btn btn-primary btn-sm" title="Search">Search</button> |             <button id="do-search-button" class="btn btn-sm icon-button" title="Search (enter)" | ||||||
|           </div> |                     style="background-image: url('/images/icons/search-20.png');"></button> | ||||||
|  |  | ||||||
|           <div style="display: flex; align-items: center; justify-content: space-evenly; margin-top: 10px;"> |               | ||||||
|             <button id="reset-search-button" class="btn btn-sm" title="Reset search">Reset search</button> |  | ||||||
|  |  | ||||||
|             <button id="save-search-button" class="btn btn-sm" title="Save search">Save search</button> |             <button id="save-search-button" class="btn btn-sm icon-button" title="Save search" | ||||||
|  |                     style="background-image: url('/images/icons/save-20.png');"></button> | ||||||
|  |  | ||||||
|  |               | ||||||
|  |  | ||||||
|  |             <button id="close-search-button" class="btn btn-sm icon-button" title="Close search" | ||||||
|  |                     style="background-image: url('/images/icons/x-20.png');"></button> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|  |         <div id="search-results"> | ||||||
|  |           <strong>Search results:</strong> | ||||||
|  |  | ||||||
|  |           <ul id="search-results-inner"> | ||||||
|  |             <li>aaa</li> | ||||||
|  |             <li>bbb</li> | ||||||
|  |             <li>ccc</li> | ||||||
|  |           </ul> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|         <div id="tree"></div> |         <div id="tree"></div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       <div style="grid-area: title;"> |       <div style="grid-area: title;"> | ||||||
|         <div class="hide-toggle" style="display: flex; align-items: center;"> |         <div style="display: flex; align-items: center;"> | ||||||
|           <div class="dropdown"> |           <div class="dropdown hide-toggle"> | ||||||
|             <button id="note-path-list-button" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle"> |             <button id="note-path-list-button" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle"> | ||||||
|               <span id="note-path-count">1 path</span> |               <span id="note-path-count">1 path</span> | ||||||
|  |  | ||||||
| @@ -103,60 +112,69 @@ | |||||||
|  |  | ||||||
|           <input autocomplete="off" value="" id="note-title" style="margin-left: 15px; font-size: x-large; border: 0; flex-grow: 100;" tabindex="1"> |           <input autocomplete="off" value="" id="note-title" style="margin-left: 15px; font-size: x-large; border: 0; flex-grow: 100;" tabindex="1"> | ||||||
|  |  | ||||||
|           <span id="note-id-display" title="Note ID"></span> |           <div class="hide-toggle" style="display: flex; align-items: center;"> | ||||||
|  |             <span id="note-id-display" title="Note ID"></span> | ||||||
|  |  | ||||||
|           <a title="Protect the note so that password will be required to view the note" |             <button class="btn btn-sm icon-button" | ||||||
|              class="icon-action" |                     style="display: none; margin-right: 10px; background-image: url('/images/icons/edit-20.png');" | ||||||
|              id="protect-button" |                     title="Toggle edit" | ||||||
|              style="display: none; background: url('images/icons/lock.png')"></a> |                     id="toggle-edit-button"></button> | ||||||
|  |  | ||||||
|           <a title="Unprotect note so that password will not be required to access this note in the future" |             <button class="btn btn-sm icon-button" | ||||||
|              class="icon-action" |                     style="display: none; margin-right: 10px; background-image: url('/images/icons/play-20.png');" | ||||||
|              id="unprotect-button" |                     title="Render (Ctrl+Enter)" | ||||||
|              style="display: none; background: url('images/icons/unlock.png')"></a> |                     id="render-button"></button> | ||||||
|  |  | ||||||
|               |             <button class="btn btn-sm icon-button" | ||||||
|  |                     style="display: none; margin-right: 10px; background-image: url('/images/icons/play-20.png');" | ||||||
|  |                     title="Execute (Ctrl+Enter)" | ||||||
|  |                     id="execute-script-button"></button> | ||||||
|  |  | ||||||
|           <button class="btn btn-sm" |             <div> | ||||||
|                   style="display: none; margin-right: 10px" |               <button type="button" | ||||||
|                   id="toggle-edit-button">Toggle edit</button> |                       class="btn btn-sm icon-button" | ||||||
|  |                       id="protect-button" | ||||||
|  |                       title="Protected note can be viewed and edited only after entering password" | ||||||
|  |                       style="background-image: url('/images/icons/shield-20.png');"> | ||||||
|  |               </button><button type="button" | ||||||
|  |                       class="btn btn-sm icon-button" | ||||||
|  |                       id="unprotect-button" | ||||||
|  |                       title="Not protected note can be viewed without entering password" | ||||||
|  |                       style="background-image: url('/images/icons/shield-off-20.png');"> | ||||||
|  |               </button> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|           <button class="btn btn-sm" |                 | ||||||
|                   style="display: none; margin-right: 10px" |  | ||||||
|                   id="render-button">Render <kbd>Ctrl+Enter</kbd></button> |  | ||||||
|  |  | ||||||
|           <button class="btn btn-sm" |             <div class="dropdown" id="note-type" data-bind="visible: type() != 'search'"> | ||||||
|                   style="display: none; margin-right: 10px" |               <button data-bind="disable: isDisabled()" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm"> | ||||||
|                   id="execute-script-button">Execute <kbd>Ctrl+Enter</kbd></button> |                 Type: <span data-bind="text: typeString()"></span> | ||||||
|  |                 <span class="caret"></span> | ||||||
|  |               </button> | ||||||
|  |               <ul id="note-type-dropdown" class="dropdown-menu dropdown-menu-right"> | ||||||
|  |                 <li data-bind="click: selectText, css: { selected: type() == 'text' }"><span class="check">✓</span> <strong>Text</strong></li> | ||||||
|  |                 <li role="separator" class="divider"></li> | ||||||
|  |                 <li data-bind="click: selectRender, css: { selected: type() == 'render' && mime() == '' }"><span class="check">✓</span> <strong>Render HTML note</strong></li> | ||||||
|  |                 <li role="separator" class="divider"></li> | ||||||
|  |                 <li data-bind="click: selectCode, css: { selected: type() == 'code' && mime() == '' }"><span class="check">✓</span> <strong>Code</strong></li> | ||||||
|  |                 <!-- ko foreach: codeMimeTypes --> | ||||||
|  |                 <li data-bind="click: $parent.selectCodeMime, css: { selected: $parent.type() == 'code' && $parent.mime() == mime }"><span class="check">✓</span> <span data-bind="text: title"></span></li> | ||||||
|  |                 <!-- /ko --> | ||||||
|  |               </ul> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|           <div class="dropdown" id="note-type" data-bind="visible: type() != 'search'"> |             <div class="dropdown" id="note-actions"> | ||||||
|             <button data-bind="disable: isDisabled()" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm"> |               <button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm"> | ||||||
|               Type: <span data-bind="text: typeString()"></span> |                 Note actions | ||||||
|               <span class="caret"></span> |                 <span class="caret"></span> | ||||||
|             </button> |               </button> | ||||||
|             <ul id="note-type-dropdown" class="dropdown-menu dropdown-menu-right"> |               <ul class="dropdown-menu dropdown-menu-right"> | ||||||
|               <li data-bind="click: selectText, css: { selected: type() == 'text' }"><span class="check">✓</span> <strong>Text</strong></li> |                 <li><a id="show-note-revisions-button">Note revisions</a></li> | ||||||
|               <li role="separator" class="divider"></li> |                 <li><a class="show-labels-button"><kbd>Alt+L</kbd> Labels</a></li> | ||||||
|               <li data-bind="click: selectRender, css: { selected: type() == 'render' && mime() == '' }"><span class="check">✓</span> <strong>Render HTML note</strong></li> |                 <li><a id="show-source-button"><kbd>Ctrl+U</kbd> HTML source</a></li> | ||||||
|               <li role="separator" class="divider"></li> |                 <li><a id="upload-file-button">Upload file</a></li> | ||||||
|               <li data-bind="click: selectCode, css: { selected: type() == 'code' && mime() == '' }"><span class="check">✓</span> <strong>Code</strong></li> |               </ul> | ||||||
|               <!-- ko foreach: codeMimeTypes --> |             </div> | ||||||
|               <li data-bind="click: $parent.selectCodeMime, css: { selected: $parent.type() == 'code' && $parent.mime() == mime }"><span class="check">✓</span> <span data-bind="text: title"></span></li> |  | ||||||
|               <!-- /ko --> |  | ||||||
|             </ul> |  | ||||||
|           </div> |  | ||||||
|  |  | ||||||
|           <div class="dropdown" style="margin-left: 10px; margin-right: 10px;"> |  | ||||||
|             <button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm"> |  | ||||||
|               Note actions |  | ||||||
|               <span class="caret"></span> |  | ||||||
|             </button> |  | ||||||
|             <ul class="dropdown-menu dropdown-menu-right"> |  | ||||||
|               <li><a id="show-note-revisions-button">Note revisions</a></li> |  | ||||||
|               <li><a class="show-labels-button"><kbd>Alt+L</kbd> Labels</a></li> |  | ||||||
|               <li><a id="show-source-button"><kbd>Ctrl+U</kbd> HTML source</a></li> |  | ||||||
|               <li><a id="upload-file-button">Upload file</a></li> |  | ||||||
|             </ul> |  | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
| @@ -248,7 +266,7 @@ | |||||||
|       <input id="recent-notes-search-input" class="form-control"/> |       <input id="recent-notes-search-input" class="form-control"/> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <div id="add-link-dialog" title="Add link" style="display: none;"> |     <div id="add-link-dialog" title="Add note link" style="display: none;"> | ||||||
|       <form id="add-link-form"> |       <form id="add-link-form"> | ||||||
|         <div id="add-link-type-div" class="radio"> |         <div id="add-link-type-div" class="radio"> | ||||||
|           <label title="Add HTML link to the selected note at cursor in current note"> |           <label title="Add HTML link to the selected note at cursor in current note"> | ||||||
| @@ -266,7 +284,7 @@ | |||||||
|  |  | ||||||
|         <div class="form-group"> |         <div class="form-group"> | ||||||
|           <label for="note-autocomplete">Note</label> |           <label for="note-autocomplete">Note</label> | ||||||
|           <input id="note-autocomplete" style="width: 100%;"> |           <input id="note-autocomplete" placeholder="search for note by its name" style="width: 100%;"> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|         <div class="form-group" id="add-link-title-form-group"> |         <div class="form-group" id="add-link-title-form-group"> | ||||||
| @@ -279,7 +297,7 @@ | |||||||
|           <input id="clone-prefix" style="width: 100%;"> |           <input id="clone-prefix" style="width: 100%;"> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|         <button class="btn btn-sm">Add link</button> |         <button class="btn btn-sm">Add note link</button> | ||||||
|       </form> |       </form> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
| @@ -287,10 +305,14 @@ | |||||||
|       <form id="jump-to-note-form"> |       <form id="jump-to-note-form"> | ||||||
|         <div class="form-group"> |         <div class="form-group"> | ||||||
|           <label for="jump-to-note-autocomplete">Note</label> |           <label for="jump-to-note-autocomplete">Note</label> | ||||||
|           <input id="jump-to-note-autocomplete" style="width: 100%;"> |           <input id="jump-to-note-autocomplete" placeholder="search for note by its name" style="width: 100%;"> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|         <button name="action" value="jump" class="btn btn-sm">Jump <kbd>enter</kbd></button> |         <div style="display: flex; justify-content: space-between;"> | ||||||
|  |           <button id="jump-to-note-button" class="btn btn-sm btn-primary">Jump <kbd>enter</kbd></button> | ||||||
|  |  | ||||||
|  |           <button id="show-in-full-text-button" class="btn btn-sm">Search in full text <kbd>ctrl+enter</kbd></button> | ||||||
|  |         </div> | ||||||
|       </form> |       </form> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
| @@ -308,12 +330,35 @@ | |||||||
|     <div id="options-dialog" title="Options" style="display: none;"> |     <div id="options-dialog" title="Options" style="display: none;"> | ||||||
|       <div id="options-tabs"> |       <div id="options-tabs"> | ||||||
|         <ul> |         <ul> | ||||||
|  |           <li><a href="#appearance">Apperance</a></li> | ||||||
|           <li><a href="#change-password">Change password</a></li> |           <li><a href="#change-password">Change password</a></li> | ||||||
|           <li><a href="#protected-session-timeout">Protected session</a></li> |           <li><a href="#protected-session-timeout">Protected session</a></li> | ||||||
|           <li><a href="#note-revision-snapshot-time-interval">Note revisions</a></li> |           <li><a href="#note-revision-snapshot-time-interval">Note revisions</a></li> | ||||||
|           <li><a href="#advanced">Advanced</a></li> |           <li><a href="#advanced">Advanced</a></li> | ||||||
|           <li><a href="#about">About Trilium</a></li> |           <li><a href="#about">About Trilium</a></li> | ||||||
|         </ul> |         </ul> | ||||||
|  |         <div id="appearance"> | ||||||
|  |           <p>Settings on this options tab are saved automatically after each change.</p> | ||||||
|  |  | ||||||
|  |           <form> | ||||||
|  |             <div class="form-group"> | ||||||
|  |               <label for="theme-select">Theme</label> | ||||||
|  |               <select class="form-control" id="theme-select"> | ||||||
|  |                 <option value="white">White</option> | ||||||
|  |                 <option value="dark">Dark</option> | ||||||
|  |                 <option value="black">Black</option> | ||||||
|  |               </select> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <div class="form-group"> | ||||||
|  |               <label for="zoom-factor-select">Zoom factor (desktop build only)</label> | ||||||
|  |  | ||||||
|  |               <input type="number" class="form-control" id="zoom-factor-select" min="0.3" max="2.0" step="0.1"/> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <p>Zooming can be controlled with CTRL-+ and CTRL-= shortcuts as well.</p> | ||||||
|  |           </form> | ||||||
|  |         </div> | ||||||
|         <div id="change-password"> |         <div id="change-password"> | ||||||
|           <form id="change-password-form"> |           <form id="change-password-form"> | ||||||
|             <div class="form-group"> |             <div class="form-group"> | ||||||
| @@ -402,12 +447,14 @@ | |||||||
|               <th>App version:</th> |               <th>App version:</th> | ||||||
|               <td id="app-version"></td> |               <td id="app-version"></td> | ||||||
|             </tr> |             </tr> | ||||||
|  |  | ||||||
|             <tr> |             <tr> | ||||||
|               <th>DB version:</th> |               <th>DB version:</th> | ||||||
|               <td id="db-version"></td> |               <td id="db-version"></td> | ||||||
|             </tr> |             </tr> | ||||||
|  |             <tr> | ||||||
|  |               <th>Sync version:</th> | ||||||
|  |               <td id="sync-version"></td> | ||||||
|  |             </tr> | ||||||
|             <tr> |             <tr> | ||||||
|               <th>Build date:</th> |               <th>Build date:</th> | ||||||
|               <td id="build-date"></td> |               <td id="build-date"></td> | ||||||
|   | |||||||
| @@ -1,72 +0,0 @@ | |||||||
| <!DOCTYPE html> |  | ||||||
| <html lang="en"> |  | ||||||
|   <head> |  | ||||||
|     <meta charset="utf-8"> |  | ||||||
|     <title>Migration</title> |  | ||||||
|   </head> |  | ||||||
|   <body> |  | ||||||
|     <div style="width: 800px; margin: auto;"> |  | ||||||
|       <h1>Migration</h1> |  | ||||||
|  |  | ||||||
|       <div id="up-to-date" style="display:none;"> |  | ||||||
|         <p>Your database is up-to-date with the application.</p> |  | ||||||
|  |  | ||||||
|         <a href="/" class="btn btn-success">Continue to app</a> |  | ||||||
|       </div> |  | ||||||
|  |  | ||||||
|       <div id="need-to-migrate" style="display:none;"> |  | ||||||
|         <p>Your database needs to be migrated to new version before you can use the application again. |  | ||||||
|         Database will be backed up before migration in case of something going wrong.</p> |  | ||||||
|  |  | ||||||
|         <table class="table table-bordered" style="width: 200px;"> |  | ||||||
|           <tr> |  | ||||||
|             <th>Application version:</th> |  | ||||||
|             <td id="app-db-version" style="text-align: right;"></td> |  | ||||||
|           <tr> |  | ||||||
|             <th>Database version:</th> |  | ||||||
|             <td id="db-version" style="text-align: right;"></td> |  | ||||||
|           </tr> |  | ||||||
|         </table> |  | ||||||
|  |  | ||||||
|         <button class="btn btn-warning" id="run-migration">Run migration</button> |  | ||||||
|       </div> |  | ||||||
|  |  | ||||||
|       <div id="migration-result" style="display:none;"> |  | ||||||
|         <h2>Migration result</h2> |  | ||||||
|  |  | ||||||
|         <table id="migration-table" class="table"> |  | ||||||
|           <tr> |  | ||||||
|             <th>Database version</th> |  | ||||||
|             <th>Name</th> |  | ||||||
|             <th>Success</th> |  | ||||||
|             <th>Error</th> |  | ||||||
|           </tr> |  | ||||||
|         </table> |  | ||||||
|  |  | ||||||
|         <a href="/" class="btn btn-success">Continue to app</a> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|  |  | ||||||
|     <script type="text/javascript"> |  | ||||||
|       const baseApiUrl = 'api/'; |  | ||||||
|     </script> |  | ||||||
|  |  | ||||||
|     <!-- Required for correct loading of scripts in Electron --> |  | ||||||
|     <script> |  | ||||||
|         if (typeof module === 'object') { |  | ||||||
|             window.module = module; module = undefined; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         const glob = { |  | ||||||
|             sourceId: '' |  | ||||||
|         }; |  | ||||||
|     </script> |  | ||||||
|  |  | ||||||
|     <script src="libraries/jquery.min.js"></script> |  | ||||||
|  |  | ||||||
|     <link href="libraries/bootstrap/css/bootstrap.css" rel="stylesheet"> |  | ||||||
|     <script src="libraries/bootstrap/js/bootstrap.js"></script> |  | ||||||
|  |  | ||||||
|     <script src="javascripts/migration.js" type="module"></script> |  | ||||||
|   </body> |  | ||||||
| </html> |  | ||||||
							
								
								
									
										3
									
								
								src/www
									
									
									
									
									
								
							
							
						
						| @@ -18,6 +18,7 @@ const log = require('./services/log'); | |||||||
| const appInfo = require('./services/app_info'); | const appInfo = require('./services/app_info'); | ||||||
| const messagingService = require('./services/messaging'); | const messagingService = require('./services/messaging'); | ||||||
| const utils = require('./services/utils'); | const utils = require('./services/utils'); | ||||||
|  | const sqlInit = require('./services/sql_init.js'); | ||||||
|  |  | ||||||
| const port = normalizePort(config['Network']['port'] || '3000'); | const port = normalizePort(config['Network']['port'] || '3000'); | ||||||
| app.set('port', port); | app.set('port', port); | ||||||
| @@ -54,7 +55,7 @@ httpServer.listen(port); | |||||||
| httpServer.on('error', onError); | httpServer.on('error', onError); | ||||||
| httpServer.on('listening', onListening); | httpServer.on('listening', onListening); | ||||||
|  |  | ||||||
| messagingService.init(httpServer, sessionParser); | sqlInit.dbReady.then(() => messagingService.init(httpServer, sessionParser)); | ||||||
|  |  | ||||||
| if (utils.isElectron()) { | if (utils.isElectron()) { | ||||||
|     const electronRouting = require('./routes/electron'); |     const electronRouting = require('./routes/electron'); | ||||||
|   | |||||||