mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Compare commits
	
		
			41 Commits
		
	
	
		
			v0.10.0-be
			...
			v0.11.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 5b43f321e2 | ||
|  | a4eafb934f | ||
|  | 7b59a665dd | ||
|  | 3d15450ffc | ||
|  | b0c6d52461 | ||
|  | 2dc16dd29f | ||
|  | d8924c536b | ||
|  | 3ebbf2cc46 | ||
|  | f4079604c9 | ||
|  | 1f96a6beab | ||
|  | b277a250e5 | ||
|  | 5b0e1a644d | ||
|  | 6bb3cfa9a3 | ||
|  | 9720868f5a | ||
|  | 8d8ee2a87a | ||
|  | 542e82ee5d | ||
|  | 0104b19502 | ||
|  | 120888b53e | ||
|  | d2e2caed62 | ||
|  | 63066802a8 | ||
|  | 6128bb4ff3 | ||
|  | 982796255d | ||
|  | 36b15f474d | ||
|  | 13f71f8967 | ||
|  | 64336ffbee | ||
|  | b09463d1b2 | ||
|  | b5e6f46b9c | ||
|  | 08af4a0465 | ||
|  | 8c5df6321f | ||
|  | d19f044961 | ||
|  | e378d9f645 | ||
|  | 39dc0f71b4 | ||
|  | 0cef5c6b8c | ||
|  | 9b5a44cef4 | ||
|  | 29769ed91d | ||
|  | 867d794e17 | ||
|  | fdd8458336 | ||
|  | a0bec22e96 | ||
|  | 5aeb5cd214 | ||
|  | e827ddffb9 | ||
|  | 98f80998b9 | 
| @@ -1,7 +1,9 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <dataSource name="document.db"> | <dataSource name="document.db"> | ||||||
|   <database-model serializer="dbm" rdbms="SQLITE" format-version="4.7"> |   <database-model serializer="dbm" rdbms="SQLITE" format-version="4.8"> | ||||||
|     <root id="1"/> |     <root id="1"> | ||||||
|  |       <ServerVersion>3.16.1</ServerVersion> | ||||||
|  |     </root> | ||||||
|     <schema id="2" parent="1" name="main"> |     <schema id="2" parent="1" name="main"> | ||||||
|       <Current>1</Current> |       <Current>1</Current> | ||||||
|       <Visible>1</Visible> |       <Visible>1</Visible> | ||||||
| @@ -107,8 +109,7 @@ | |||||||
|     <index id="35" parent="7" name="IDX_branches_noteId_parentNoteId"> |     <index id="35" parent="7" name="IDX_branches_noteId_parentNoteId"> | ||||||
|       <ColNames>noteId |       <ColNames>noteId | ||||||
| parentNoteId</ColNames> | parentNoteId</ColNames> | ||||||
|       <ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
| </ColumnCollations> |  | ||||||
|     </index> |     </index> | ||||||
|     <index id="36" parent="7" name="IDX_branches_noteId"> |     <index id="36" parent="7" name="IDX_branches_noteId"> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
| @@ -142,445 +143,449 @@ parentNoteId</ColNames> | |||||||
|       <ColNames>id</ColNames> |       <ColNames>id</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|     </key> |     </key> | ||||||
|     <foreign-key id="43" parent="8"> |     <column id="43" parent="9" name="imageId"> | ||||||
|       <ColNames>noteId</ColNames> |  | ||||||
|       <RefTableName>notes</RefTableName> |  | ||||||
|       <RefColNames>noteId</RefColNames> |  | ||||||
|     </foreign-key> |  | ||||||
|     <column id="44" parent="9" name="imageId"> |  | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="45" parent="9" name="format"> |     <column id="44" parent="9" name="format"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="46" parent="9" name="checksum"> |     <column id="45" parent="9" name="checksum"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="47" parent="9" name="name"> |     <column id="46" parent="9" name="name"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="48" parent="9" name="data"> |     <column id="47" parent="9" name="data"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>BLOB|0s</DataType> |       <DataType>BLOB|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="49" parent="9" name="isDeleted"> |     <column id="48" parent="9" name="isDeleted"> | ||||||
|       <Position>6</Position> |       <Position>6</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="50" parent="9" name="dateModified"> |     <column id="49" parent="9" name="dateModified"> | ||||||
|       <Position>7</Position> |       <Position>7</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="51" parent="9" name="dateCreated"> |     <column id="50" parent="9" name="dateCreated"> | ||||||
|       <Position>8</Position> |       <Position>8</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <index id="52" parent="9" name="sqlite_autoindex_images_1"> |     <index id="51" parent="9" name="sqlite_autoindex_images_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>imageId</ColNames> |       <ColNames>imageId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <key id="53" parent="9"> |     <key id="52" parent="9"> | ||||||
|       <ColNames>imageId</ColNames> |       <ColNames>imageId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="54" parent="10" name="labelId"> |     <column id="53" parent="10" name="labelId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="55" parent="10" name="noteId"> |     <column id="54" parent="10" name="noteId"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="56" parent="10" name="name"> |     <column id="55" parent="10" name="name"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="57" parent="10" name="value"> |     <column id="56" parent="10" name="value"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>''</DefaultExpression> |       <DefaultExpression>''</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="58" parent="10" name="position"> |     <column id="57" parent="10" name="position"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="59" parent="10" name="dateCreated"> |     <column id="58" parent="10" name="dateCreated"> | ||||||
|       <Position>6</Position> |       <Position>6</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="60" parent="10" name="dateModified"> |     <column id="59" parent="10" name="dateModified"> | ||||||
|       <Position>7</Position> |       <Position>7</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="61" parent="10" name="isDeleted"> |     <column id="60" parent="10" name="isDeleted"> | ||||||
|       <Position>8</Position> |       <Position>8</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <index id="62" parent="10" name="sqlite_autoindex_labels_1"> |     <index id="61" parent="10" name="sqlite_autoindex_labels_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>labelId</ColNames> |       <ColNames>labelId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <index id="63" parent="10" name="IDX_labels_noteId"> |     <index id="62" parent="10" name="IDX_labels_noteId"> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <index id="64" parent="10" name="IDX_labels_name_value"> |     <index id="63" parent="10" name="IDX_labels_name_value"> | ||||||
|       <ColNames>name |       <ColNames>name | ||||||
| value</ColNames> | value</ColNames> | ||||||
|       <ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
| </ColumnCollations> |  | ||||||
|     </index> |     </index> | ||||||
|     <key id="65" parent="10"> |     <key id="64" parent="10"> | ||||||
|       <ColNames>labelId</ColNames> |       <ColNames>labelId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="66" parent="11" name="noteImageId"> |     <column id="65" parent="11" name="noteImageId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="67" parent="11" name="noteId"> |     <column id="66" parent="11" name="noteId"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="68" parent="11" name="imageId"> |     <column id="67" parent="11" name="imageId"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="69" parent="11" name="isDeleted"> |     <column id="68" parent="11" name="isDeleted"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="70" parent="11" name="dateModified"> |     <column id="69" parent="11" name="dateModified"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="71" parent="11" name="dateCreated"> |     <column id="70" parent="11" name="dateCreated"> | ||||||
|       <Position>6</Position> |       <Position>6</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <index id="72" parent="11" name="sqlite_autoindex_note_images_1"> |     <index id="71" parent="11" name="sqlite_autoindex_note_images_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>noteImageId</ColNames> |       <ColNames>noteImageId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <index id="73" parent="11" name="IDX_note_images_noteId_imageId"> |     <index id="72" parent="11" name="IDX_note_images_noteId_imageId"> | ||||||
|       <ColNames>noteId |       <ColNames>noteId | ||||||
| imageId</ColNames> | imageId</ColNames> | ||||||
|       <ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
| </ColumnCollations> |  | ||||||
|     </index> |     </index> | ||||||
|     <index id="74" parent="11" name="IDX_note_images_noteId"> |     <index id="73" parent="11" name="IDX_note_images_noteId"> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <index id="75" parent="11" name="IDX_note_images_imageId"> |     <index id="74" parent="11" name="IDX_note_images_imageId"> | ||||||
|       <ColNames>imageId</ColNames> |       <ColNames>imageId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <key id="76" parent="11"> |     <key id="75" parent="11"> | ||||||
|       <ColNames>noteImageId</ColNames> |       <ColNames>noteImageId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_note_images_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_note_images_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="77" parent="12" name="noteRevisionId"> |     <column id="76" parent="12" name="noteRevisionId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="78" parent="12" name="noteId"> |     <column id="77" parent="12" name="noteId"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="79" parent="12" name="title"> |     <column id="78" parent="12" name="title"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="80" parent="12" name="content"> |     <column id="79" parent="12" name="content"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="81" parent="12" name="isProtected"> |     <column id="80" parent="12" name="isProtected"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="82" parent="12" name="dateModifiedFrom"> |     <column id="81" parent="12" name="dateModifiedFrom"> | ||||||
|       <Position>6</Position> |       <Position>6</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="83" parent="12" name="dateModifiedTo"> |     <column id="82" parent="12" name="dateModifiedTo"> | ||||||
|       <Position>7</Position> |       <Position>7</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <index id="84" parent="12" name="sqlite_autoindex_note_revisions_1"> |     <column id="83" parent="12" name="type"> | ||||||
|  |       <Position>8</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>''</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <column id="84" parent="12" name="mime"> | ||||||
|  |       <Position>9</Position> | ||||||
|  |       <DataType>TEXT|0s</DataType> | ||||||
|  |       <NotNull>1</NotNull> | ||||||
|  |       <DefaultExpression>''</DefaultExpression> | ||||||
|  |     </column> | ||||||
|  |     <index id="85" parent="12" name="sqlite_autoindex_note_revisions_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>noteRevisionId</ColNames> |       <ColNames>noteRevisionId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <index id="85" parent="12" name="IDX_note_revisions_noteId"> |     <index id="86" parent="12" name="IDX_note_revisions_noteId"> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <index id="86" parent="12" name="IDX_note_revisions_dateModifiedFrom"> |     <index id="87" parent="12" name="IDX_note_revisions_dateModifiedFrom"> | ||||||
|       <ColNames>dateModifiedFrom</ColNames> |       <ColNames>dateModifiedFrom</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <index id="87" parent="12" name="IDX_note_revisions_dateModifiedTo"> |     <index id="88" parent="12" name="IDX_note_revisions_dateModifiedTo"> | ||||||
|       <ColNames>dateModifiedTo</ColNames> |       <ColNames>dateModifiedTo</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <key id="88" parent="12"> |     <key id="89" parent="12"> | ||||||
|       <ColNames>noteRevisionId</ColNames> |       <ColNames>noteRevisionId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="89" parent="13" name="noteId"> |     <column id="90" parent="13" name="noteId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="90" parent="13" name="title"> |     <column id="91" parent="13" name="title"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="91" parent="13" name="content"> |     <column id="92" parent="13" name="content"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="92" parent="13" name="isProtected"> |     <column id="93" parent="13" name="isProtected"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="93" parent="13" name="isDeleted"> |     <column id="94" parent="13" name="isDeleted"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="94" parent="13" name="dateCreated"> |     <column id="95" parent="13" name="dateCreated"> | ||||||
|       <Position>6</Position> |       <Position>6</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="95" parent="13" name="dateModified"> |     <column id="96" parent="13" name="dateModified"> | ||||||
|       <Position>7</Position> |       <Position>7</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="96" parent="13" name="type"> |     <column id="97" parent="13" name="type"> | ||||||
|       <Position>8</Position> |       <Position>8</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>'text'</DefaultExpression> |       <DefaultExpression>'text'</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <column id="97" parent="13" name="mime"> |     <column id="98" parent="13" name="mime"> | ||||||
|       <Position>9</Position> |       <Position>9</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>'text/html'</DefaultExpression> |       <DefaultExpression>'text/html'</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <index id="98" parent="13" name="sqlite_autoindex_notes_1"> |     <index id="99" parent="13" name="sqlite_autoindex_notes_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <index id="99" parent="13" name="IDX_notes_isDeleted"> |     <index id="100" parent="13" name="IDX_notes_isDeleted"> | ||||||
|       <ColNames>isDeleted</ColNames> |       <ColNames>isDeleted</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <key id="100" parent="13"> |     <key id="101" parent="13"> | ||||||
|       <ColNames>noteId</ColNames> |       <ColNames>noteId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="101" parent="14" name="name"> |     <column id="102" parent="14" name="name"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="102" parent="14" name="value"> |     <column id="103" parent="14" name="value"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="103" parent="14" name="dateModified"> |     <column id="104" parent="14" name="dateModified"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="104" parent="14" name="isSynced"> |     <column id="105" parent="14" name="isSynced"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>INTEGER|0s</DataType> |       <DataType>INTEGER|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|       <DefaultExpression>0</DefaultExpression> |       <DefaultExpression>0</DefaultExpression> | ||||||
|     </column> |     </column> | ||||||
|     <index id="105" parent="14" name="sqlite_autoindex_options_1"> |     <index id="106" parent="14" name="sqlite_autoindex_options_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>name</ColNames> |       <ColNames>name</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <key id="106" parent="14"> |     <key id="107" parent="14"> | ||||||
|       <ColNames>name</ColNames> |       <ColNames>name</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="107" parent="15" name="branchId"> |     <column id="108" parent="15" name="branchId"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="108" parent="15" name="notePath"> |     <column id="109" parent="15" name="notePath"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="109" parent="15" name="dateAccessed"> |     <column id="110" parent="15" name="dateAccessed"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>TEXT|0s</DataType> |       <DataType>TEXT|0s</DataType> | ||||||
|       <NotNull>1</NotNull> |       <NotNull>1</NotNull> | ||||||
|     </column> |     </column> | ||||||
|     <column id="110" parent="15" name="isDeleted"> |     <column id="111" parent="15" name="isDeleted"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>INT|0s</DataType> |       <DataType>INT|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <index id="111" parent="15" name="sqlite_autoindex_recent_notes_1"> |     <index id="112" parent="15" name="sqlite_autoindex_recent_notes_1"> | ||||||
|       <NameSurrogate>1</NameSurrogate> |       <NameSurrogate>1</NameSurrogate> | ||||||
|       <ColNames>branchId</ColNames> |       <ColNames>branchId</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|       <Unique>1</Unique> |       <Unique>1</Unique> | ||||||
|     </index> |     </index> | ||||||
|     <key id="112" parent="15"> |     <key id="113" parent="15"> | ||||||
|       <ColNames>branchId</ColNames> |       <ColNames>branchId</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|       <UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName> |       <UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName> | ||||||
|     </key> |     </key> | ||||||
|     <column id="113" parent="16" name="sourceId"> |     <column id="114" 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="114" parent="16" name="dateCreated"> |     <column id="115" 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="115" parent="16" name="sqlite_autoindex_source_ids_1"> |     <index id="116" 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="116" parent="16"> |     <key id="117" 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="117" parent="17" name="type"> |     <column id="118" parent="17" name="type"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|       <DataType>text|0s</DataType> |       <DataType>text|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="118" parent="17" name="name"> |     <column id="119" parent="17" name="name"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|       <DataType>text|0s</DataType> |       <DataType>text|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="119" parent="17" name="tbl_name"> |     <column id="120" parent="17" name="tbl_name"> | ||||||
|       <Position>3</Position> |       <Position>3</Position> | ||||||
|       <DataType>text|0s</DataType> |       <DataType>text|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="120" parent="17" name="rootpage"> |     <column id="121" parent="17" name="rootpage"> | ||||||
|       <Position>4</Position> |       <Position>4</Position> | ||||||
|       <DataType>integer|0s</DataType> |       <DataType>integer|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="121" parent="17" name="sql"> |     <column id="122" parent="17" name="sql"> | ||||||
|       <Position>5</Position> |       <Position>5</Position> | ||||||
|       <DataType>text|0s</DataType> |       <DataType>text|0s</DataType> | ||||||
|     </column> |     </column> | ||||||
|     <column id="122" parent="18" name="name"> |     <column id="123" parent="18" name="name"> | ||||||
|       <Position>1</Position> |       <Position>1</Position> | ||||||
|     </column> |     </column> | ||||||
|     <column id="123" parent="18" name="seq"> |     <column id="124" parent="18" name="seq"> | ||||||
|       <Position>2</Position> |       <Position>2</Position> | ||||||
|     </column> |     </column> | ||||||
|     <column id="124" parent="19" name="id"> |     <column id="125" 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="125" parent="19" name="entityName"> |     <column id="126" 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="126" parent="19" name="entityId"> |     <column id="127" 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="127" parent="19" name="sourceId"> |     <column id="128" 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="128" parent="19" name="syncDate"> |     <column id="129" 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="129" parent="19" name="IDX_sync_entityName_entityId"> |     <index id="130" 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="130" parent="19" name="IDX_sync_syncDate"> |     <index id="131" parent="19" name="IDX_sync_syncDate"> | ||||||
|       <ColNames>syncDate</ColNames> |       <ColNames>syncDate</ColNames> | ||||||
|       <ColumnCollations></ColumnCollations> |       <ColumnCollations></ColumnCollations> | ||||||
|     </index> |     </index> | ||||||
|     <key id="131" parent="19"> |     <key id="132" parent="19"> | ||||||
|       <ColNames>id</ColNames> |       <ColNames>id</ColNames> | ||||||
|       <Primary>1</Primary> |       <Primary>1</Primary> | ||||||
|     </key> |     </key> | ||||||
|   | |||||||
| @@ -24,9 +24,9 @@ jq '.version = "'$VERSION'"' package.json|sponge package.json | |||||||
|  |  | ||||||
| git add package.json | git add package.json | ||||||
|  |  | ||||||
| echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > services/build.js | echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > src/services/build.js | ||||||
|  |  | ||||||
| git add services/build.js | git add src/services/build.js | ||||||
|  |  | ||||||
| TAG=v$VERSION | TAG=v$VERSION | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								db/migrations/0087__add_type_mime_to_note_revision.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								db/migrations/0087__add_type_mime_to_note_revision.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | ALTER TABLE note_revisions ADD type TEXT DEFAULT '' NOT NULL; | ||||||
|  | ALTER TABLE note_revisions ADD mime TEXT DEFAULT '' NOT NULL; | ||||||
|  |  | ||||||
|  | UPDATE note_revisions SET type = (SELECT type FROM notes WHERE notes.noteId = note_revisions.noteId); | ||||||
|  | UPDATE note_revisions SET mime = (SELECT mime FROM notes WHERE notes.noteId = note_revisions.noteId); | ||||||
							
								
								
									
										34
									
								
								db/migrations/0088__non_null_note_title_content.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								db/migrations/0088__non_null_note_title_content.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | CREATE TABLE event_logc027 | ||||||
|  | ( | ||||||
|  |   id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, | ||||||
|  |   noteId TEXT, | ||||||
|  |   comment TEXT, | ||||||
|  |   dateAdded TEXT NOT NULL | ||||||
|  | ); | ||||||
|  | INSERT INTO event_logc027(id, noteId, comment, dateAdded) SELECT id, noteId, comment, dateAdded FROM event_log; | ||||||
|  | DROP TABLE event_log; | ||||||
|  | ALTER TABLE event_logc027 RENAME TO event_log; | ||||||
|  |  | ||||||
|  | CREATE TABLE IF NOT EXISTS "notes_mig" ( | ||||||
|  |   `noteId`	TEXT NOT NULL, | ||||||
|  |   `title`	TEXT NOT NULL DEFAULT "unnamed", | ||||||
|  |   `content`	TEXT NOT NULL DEFAULT "", | ||||||
|  |   `isProtected`	INT NOT NULL DEFAULT 0, | ||||||
|  |   `isDeleted`	INT NOT NULL DEFAULT 0, | ||||||
|  |   `dateCreated`	TEXT NOT NULL, | ||||||
|  |   `dateModified`	TEXT NOT NULL, | ||||||
|  |   type TEXT NOT NULL DEFAULT 'text', | ||||||
|  |   mime TEXT NOT NULL DEFAULT 'text/html', | ||||||
|  |   PRIMARY KEY(`noteId`) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | INSERT INTO notes_mig (noteId, title, content, isProtected, isDeleted, dateCreated, dateModified, type, mime) | ||||||
|  |     SELECT noteId, title, content, isProtected, isDeleted, dateCreated, dateModified, type, mime FROM notes; | ||||||
|  |  | ||||||
|  | DROP TABLE notes; | ||||||
|  |  | ||||||
|  | ALTER TABLE notes_mig RENAME TO notes; | ||||||
|  |  | ||||||
|  | CREATE INDEX `IDX_notes_isDeleted` ON `notes` ( | ||||||
|  |   `isDeleted` | ||||||
|  | ); | ||||||
| @@ -21,28 +21,6 @@ CREATE TABLE IF NOT EXISTS "source_ids" ( | |||||||
|   `dateCreated`	TEXT NOT NULL, |   `dateCreated`	TEXT NOT NULL, | ||||||
|   PRIMARY KEY(`sourceId`) |   PRIMARY KEY(`sourceId`) | ||||||
| ); | ); | ||||||
| CREATE TABLE IF NOT EXISTS "notes" ( |  | ||||||
|   `noteId`	TEXT NOT NULL, |  | ||||||
|   `title`	TEXT, |  | ||||||
|   `content`	TEXT, |  | ||||||
|   `isProtected`	INT NOT NULL DEFAULT 0, |  | ||||||
|   `isDeleted`	INT NOT NULL DEFAULT 0, |  | ||||||
|   `dateCreated`	TEXT NOT NULL, |  | ||||||
|   `dateModified`	TEXT NOT NULL, |  | ||||||
|   type TEXT NOT NULL DEFAULT 'text', |  | ||||||
|   mime TEXT NOT NULL DEFAULT 'text/html', |  | ||||||
|   PRIMARY KEY(`noteId`) |  | ||||||
| ); |  | ||||||
| CREATE INDEX `IDX_notes_isDeleted` ON `notes` ( |  | ||||||
|   `isDeleted` |  | ||||||
| ); |  | ||||||
| CREATE TABLE IF NOT EXISTS "event_log" ( |  | ||||||
|   `id`	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, |  | ||||||
|   `noteId`	TEXT, |  | ||||||
|   `comment`	TEXT, |  | ||||||
|   `dateAdded`	TEXT NOT NULL, |  | ||||||
|   FOREIGN KEY(noteId) REFERENCES notes(noteId) |  | ||||||
| ); |  | ||||||
| CREATE TABLE IF NOT EXISTS "note_revisions" ( | CREATE TABLE IF NOT EXISTS "note_revisions" ( | ||||||
|   `noteRevisionId`	TEXT NOT NULL PRIMARY KEY, |   `noteRevisionId`	TEXT NOT NULL PRIMARY KEY, | ||||||
|   `noteId`	TEXT NOT NULL, |   `noteId`	TEXT NOT NULL, | ||||||
| @@ -51,7 +29,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); | ||||||
| CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` ( | CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` ( | ||||||
|   `noteId` |   `noteId` | ||||||
| ); | ); | ||||||
| @@ -130,3 +108,25 @@ 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" ( | ||||||
|  |   `noteId`	TEXT NOT NULL, | ||||||
|  |   `title`	TEXT NOT NULL DEFAULT "unnamed", | ||||||
|  |   `content`	TEXT NOT NULL DEFAULT "", | ||||||
|  |   `isProtected`	INT NOT NULL DEFAULT 0, | ||||||
|  |   `isDeleted`	INT NOT NULL DEFAULT 0, | ||||||
|  |   `dateCreated`	TEXT NOT NULL, | ||||||
|  |   `dateModified`	TEXT NOT NULL, | ||||||
|  |   type TEXT NOT NULL DEFAULT 'text', | ||||||
|  |   mime TEXT NOT NULL DEFAULT 'text/html', | ||||||
|  |   PRIMARY KEY(`noteId`) | ||||||
|  | ); | ||||||
|  | CREATE INDEX `IDX_notes_isDeleted` ON `notes` ( | ||||||
|  |   `isDeleted` | ||||||
|  | ); | ||||||
|   | |||||||
| @@ -76,12 +76,12 @@ app.on('ready', () => { | |||||||
|         const dateNoteService = require('./src/services/date_notes'); |         const dateNoteService = require('./src/services/date_notes'); | ||||||
|         const dateUtils = require('./src/services/date_utils'); |         const dateUtils = require('./src/services/date_utils'); | ||||||
|  |  | ||||||
|         const parentNoteId = await dateNoteService.getDateNoteId(dateUtils.nowDate()); |         const parentNote = await dateNoteService.getDateNote(dateUtils.nowDate()); | ||||||
|  |  | ||||||
|         // window may be hidden / not in focus |         // window may be hidden / not in focus | ||||||
|         mainWindow.focus(); |         mainWindow.focus(); | ||||||
|  |  | ||||||
|         mainWindow.webContents.send('create-day-sub-note', parentNoteId); |         mainWindow.webContents.send('create-day-sub-note', parentNote.noteId); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     if (!result) { |     if (!result) { | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "description": "Trilium Notes", |   "description": "Trilium Notes", | ||||||
|   "version": "0.10.0-beta", |   "version": "0.11.1", | ||||||
|   "license": "AGPL-3.0-only", |   "license": "AGPL-3.0-only", | ||||||
|   "main": "electron.js", |   "main": "electron.js", | ||||||
|   "repository": { |   "repository": { | ||||||
|   | |||||||
| @@ -12,13 +12,21 @@ class Note extends Entity { | |||||||
|     constructor(row) { |     constructor(row) { | ||||||
|         super(row); |         super(row); | ||||||
|  |  | ||||||
|         if (this.isProtected) { |         // check if there's noteId, otherwise this is a new entity which wasn't encrypted yet | ||||||
|  |         if (this.isProtected && this.noteId) { | ||||||
|             protected_session.decryptNote(this); |             protected_session.decryptNote(this); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (this.isJson()) { |         this.setContent(this.content); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     setContent(content) { | ||||||
|  |         this.content = content; | ||||||
|  |  | ||||||
|  |         try { | ||||||
|             this.jsonContent = JSON.parse(this.content); |             this.jsonContent = JSON.parse(this.content); | ||||||
|         } |         } | ||||||
|  |         catch(e) {} | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     isJson() { |     isJson() { | ||||||
| @@ -133,7 +141,7 @@ class Note extends Entity { | |||||||
|     beforeSaving() { |     beforeSaving() { | ||||||
|         super.beforeSaving(); |         super.beforeSaving(); | ||||||
|  |  | ||||||
|         if (this.isJson()) { |         if (this.isJson() && this.jsonContent) { | ||||||
|             this.content = JSON.stringify(this.jsonContent, null, '\t'); |             this.content = JSON.stringify(this.jsonContent, null, '\t'); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -54,7 +54,13 @@ $list.on('change', () => { | |||||||
|     const revisionItem = revisionItems.find(r => r.noteRevisionId === optVal); |     const revisionItem = revisionItems.find(r => r.noteRevisionId === optVal); | ||||||
|  |  | ||||||
|     $title.html(revisionItem.title); |     $title.html(revisionItem.title); | ||||||
|     $content.html(revisionItem.content); |  | ||||||
|  |     if (revisionItem.type === 'text') { | ||||||
|  |         $content.html(revisionItem.content); | ||||||
|  |     } | ||||||
|  |     else if (revisionItem.type === 'code') { | ||||||
|  |         $content.html($("<pre>").text(revisionItem.content)); | ||||||
|  |     } | ||||||
| }); | }); | ||||||
|  |  | ||||||
| $(document).on('click', "a[action='note-revision']", event => { | $(document).on('click', "a[action='note-revision']", event => { | ||||||
|   | |||||||
| @@ -14,6 +14,10 @@ class Branch { | |||||||
|         return await this.treeCache.getNote(this.noteId); |         return await this.treeCache.getNote(this.noteId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     isTopLevel() { | ||||||
|  |         return this.parentNoteId === 'root'; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     get toString() { |     get toString() { | ||||||
|         return `Branch(branchId=${this.branchId})`; |         return `Branch(branchId=${this.branchId})`; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -6,8 +6,11 @@ class NoteFull extends NoteShort { | |||||||
|  |  | ||||||
|         this.content = row.content; |         this.content = row.content; | ||||||
|  |  | ||||||
|         if (this.isJson()) { |         if (this.content !== "" && this.isJson()) { | ||||||
|             this.jsonContent = JSON.parse(this.content); |             try { | ||||||
|  |                 this.jsonContent = JSON.parse(this.content); | ||||||
|  |             } | ||||||
|  |             catch(e) {} | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -24,6 +24,10 @@ class NoteShort { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async getChildBranches() { |     async getChildBranches() { | ||||||
|  |         if (!this.treeCache.children[this.noteId]) { | ||||||
|  |             return []; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         const branches = []; |         const branches = []; | ||||||
|  |  | ||||||
|         for (const child of this.treeCache.children[this.noteId]) { |         for (const child of this.treeCache.children[this.noteId]) { | ||||||
| @@ -44,6 +48,14 @@ class NoteShort { | |||||||
|     get toString() { |     get toString() { | ||||||
|         return `Note(noteId=${this.noteId}, title=${this.title})`; |         return `Note(noteId=${this.noteId}, title=${this.title})`; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     get dto() { | ||||||
|  |         const dto = Object.assign({}, this); | ||||||
|  |         delete dto.treeCache; | ||||||
|  |         delete dto.hideInAutocomplete; | ||||||
|  |  | ||||||
|  |         return dto; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| export default NoteShort; | export default NoteShort; | ||||||
| @@ -1,20 +1,19 @@ | |||||||
| import server from './services/server.js'; | import server from './services/server.js'; | ||||||
|  |  | ||||||
| $(document).ready(() => { | $(document).ready(async () => { | ||||||
|     server.get('migration').then(result => { |     const {appDbVersion, dbVersion} = await server.get('migration'); | ||||||
|         const appDbVersion = result.app_dbVersion; |  | ||||||
|         const dbVersion = result.dbVersion; |  | ||||||
|  |  | ||||||
|         if (appDbVersion === dbVersion) { |     console.log("HI", {appDbVersion, dbVersion}); | ||||||
|             $("#up-to-date").show(); |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             $("#need-to-migrate").show(); |  | ||||||
|  |  | ||||||
|             $("#app-db-version").html(appDbVersion); |     if (appDbVersion === dbVersion) { | ||||||
|             $("#db-version").html(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").click(async () => { | ||||||
| @@ -37,4 +36,11 @@ $("#run-migration").click(async () => { | |||||||
|  |  | ||||||
|         $("#migration-table").append(row); |         $("#migration-table").append(row); | ||||||
|     } |     } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | // copy of this shortcut to be able to debug migration problems | ||||||
|  | $(document).bind('keydown', 'ctrl+shift+i', () => { | ||||||
|  |     require('electron').remote.getCurrentWindow().toggleDevTools(); | ||||||
|  |  | ||||||
|  |     return false; | ||||||
| }); | }); | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| import treeCache from "./tree_cache.js"; | import treeCache from "./tree_cache.js"; | ||||||
| import treeUtils from "./tree_utils.js"; | import treeUtils from "./tree_utils.js"; | ||||||
|  | import protectedSessionHolder from './protected_session_holder.js'; | ||||||
|  |  | ||||||
| async function getAutocompleteItems(parentNoteId, notePath, titlePath) { | async function getAutocompleteItems(parentNoteId, notePath, titlePath) { | ||||||
|     if (!parentNoteId) { |     if (!parentNoteId) { | ||||||
| @@ -21,9 +22,6 @@ async function getAutocompleteItems(parentNoteId, notePath, titlePath) { | |||||||
|         titlePath = ''; |         titlePath = ''; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // https://github.com/zadam/trilium/issues/46 |  | ||||||
|     // unfortunately not easy to implement because we don't have an easy access to note's isProtected property |  | ||||||
|  |  | ||||||
|     const autocompleteItems = []; |     const autocompleteItems = []; | ||||||
|  |  | ||||||
|     for (const childNote of childNotes) { |     for (const childNote of childNotes) { | ||||||
| @@ -34,10 +32,12 @@ async function getAutocompleteItems(parentNoteId, notePath, titlePath) { | |||||||
|         const childNotePath = (notePath ? (notePath + '/') : '') + childNote.noteId; |         const childNotePath = (notePath ? (notePath + '/') : '') + childNote.noteId; | ||||||
|         const childTitlePath = (titlePath ? (titlePath + ' / ') : '') + await treeUtils.getNoteTitle(childNote.noteId, parentNoteId); |         const childTitlePath = (titlePath ? (titlePath + ' / ') : '') + await treeUtils.getNoteTitle(childNote.noteId, parentNoteId); | ||||||
|  |  | ||||||
|         autocompleteItems.push({ |         if (!childNote.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) { | ||||||
|             value: childTitlePath + ' (' + childNotePath + ')', |             autocompleteItems.push({ | ||||||
|             label: childTitlePath |                 value: childTitlePath + ' (' + childNotePath + ')', | ||||||
|         }); |                 label: childTitlePath | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         const childItems = await getAutocompleteItems(childNote.noteId, childNotePath, childTitlePath); |         const childItems = await getAutocompleteItems(childNote.noteId, childNotePath, childTitlePath); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import treeUtils from './tree_utils.js'; | |||||||
| import branchPrefixDialog from '../dialogs/branch_prefix.js'; | import branchPrefixDialog from '../dialogs/branch_prefix.js'; | ||||||
| import infoService from "./info.js"; | import infoService from "./info.js"; | ||||||
| import treeCache from "./tree_cache.js"; | import treeCache from "./tree_cache.js"; | ||||||
|  | import syncService from "./sync.js"; | ||||||
|  |  | ||||||
| const $tree = $("#tree"); | const $tree = $("#tree"); | ||||||
|  |  | ||||||
| @@ -103,7 +104,7 @@ const contextMenuOptions = { | |||||||
|     ], |     ], | ||||||
|     beforeOpen: async (event, ui) => { |     beforeOpen: async (event, ui) => { | ||||||
|         const node = $.ui.fancytree.getNode(ui.target); |         const node = $.ui.fancytree.getNode(ui.target); | ||||||
|         const branch = await treeCache.getBranch(branchId); |         const branch = await treeCache.getBranch(node.data.branchId); | ||||||
|         const note = await treeCache.getNote(node.data.noteId); |         const note = await treeCache.getNote(node.data.noteId); | ||||||
|         const parentNote = await treeCache.getNote(branch.parentNoteId); |         const parentNote = await treeCache.getNote(branch.parentNoteId); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -32,18 +32,19 @@ async function requireLibrary(library) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| const dynamicallyLoadedScripts = []; | // we save the promises in case of the same script being required concurrently multiple times | ||||||
|  | const loadedScriptPromises = {}; | ||||||
|  |  | ||||||
| async function requireScript(url) { | async function requireScript(url) { | ||||||
|     if (!dynamicallyLoadedScripts.includes(url)) { |     if (!loadedScriptPromises[url]) { | ||||||
|         dynamicallyLoadedScripts.push(url); |         loadedScriptPromises[url] = $.ajax({ | ||||||
|  |  | ||||||
|         return await $.ajax({ |  | ||||||
|             url: url, |             url: url, | ||||||
|             dataType: "script", |             dataType: "script", | ||||||
|             cache: true |             cache: true | ||||||
|         }) |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     await loadedScriptPromises[url]; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function requireCss(url) { | async function requireCss(url) { | ||||||
|   | |||||||
| @@ -100,7 +100,7 @@ setTimeout(() => { | |||||||
|             lastSyncId: lastSyncId |             lastSyncId: lastSyncId | ||||||
|         })); |         })); | ||||||
|     }, 1000); |     }, 1000); | ||||||
| }, 1000); | }, 0); | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     logError, |     logError, | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import treeService from './tree.js'; | import treeService from './tree.js'; | ||||||
|  | import treeUtils from './tree_utils.js'; | ||||||
| import noteTypeService from './note_type.js'; | import noteTypeService from './note_type.js'; | ||||||
| import protectedSessionService from './protected_session.js'; | import protectedSessionService from './protected_session.js'; | ||||||
| import protectedSessionHolder from './protected_session_holder.js'; | import protectedSessionHolder from './protected_session_holder.js'; | ||||||
| @@ -24,6 +25,7 @@ const $noteDetailWrapper = $("#note-detail-wrapper"); | |||||||
| const $noteIdDisplay = $("#note-id-display"); | const $noteIdDisplay = $("#note-id-display"); | ||||||
| const $labelList = $("#label-list"); | const $labelList = $("#label-list"); | ||||||
| const $labelListInner = $("#label-list-inner"); | const $labelListInner = $("#label-list-inner"); | ||||||
|  | const $childrenOverview = $("#children-overview"); | ||||||
|  |  | ||||||
| let currentNote = null; | let currentNote = null; | ||||||
|  |  | ||||||
| @@ -73,50 +75,42 @@ function noteChanged() { | |||||||
| async function reload() { | async function reload() { | ||||||
|     // no saving here |     // no saving here | ||||||
|  |  | ||||||
|     await loadNoteToEditor(getCurrentNoteId()); |     await loadNoteDetail(getCurrentNoteId()); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function switchToNote(noteId) { | async function switchToNote(noteId) { | ||||||
|     if (getCurrentNoteId() !== noteId) { |     if (getCurrentNoteId() !== noteId) { | ||||||
|         await saveNoteIfChanged(); |         await saveNoteIfChanged(); | ||||||
|  |  | ||||||
|         await loadNoteToEditor(noteId); |         await loadNoteDetail(noteId); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function saveNote() { | ||||||
|  |     const note = getCurrentNote(); | ||||||
|  |  | ||||||
|  |     note.title = $noteTitle.val(); | ||||||
|  |     note.content = getComponent(note.type).getContent(); | ||||||
|  |  | ||||||
|  |     treeService.setNoteTitle(note.noteId, note.title); | ||||||
|  |  | ||||||
|  |     await server.put('notes/' + note.noteId, note.dto); | ||||||
|  |  | ||||||
|  |     isNoteChanged = false; | ||||||
|  |  | ||||||
|  |     if (note.isProtected) { | ||||||
|  |         protectedSessionHolder.touchProtectedSession(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     infoService.showMessage("Saved!"); | ||||||
|  | } | ||||||
|  |  | ||||||
| async function saveNoteIfChanged() { | async function saveNoteIfChanged() { | ||||||
|     if (!isNoteChanged) { |     if (!isNoteChanged) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const note = getCurrentNote(); |     await saveNote(); | ||||||
|  |  | ||||||
|     updateNoteFromInputs(note); |  | ||||||
|  |  | ||||||
|     await saveNoteToServer(note); |  | ||||||
|  |  | ||||||
|     if (note.isProtected) { |  | ||||||
|         protectedSessionHolder.touchProtectedSession(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function updateNoteFromInputs(note) { |  | ||||||
|     note.title = $noteTitle.val(); |  | ||||||
|     note.content = getComponent(note.type).getContent(); |  | ||||||
|  |  | ||||||
|     treeService.setNoteTitle(note.noteId, note.title); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function saveNoteToServer(note) { |  | ||||||
|     const dto = Object.assign({}, note); |  | ||||||
|     delete dto.treeCache; |  | ||||||
|     delete dto.hideInAutocomplete; |  | ||||||
|  |  | ||||||
|     await server.put('notes/' + dto.noteId, dto); |  | ||||||
|  |  | ||||||
|     isNoteChanged = false; |  | ||||||
|  |  | ||||||
|     infoService.showMessage("Saved!"); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| function setNoteBackgroundIfProtected(note) { | function setNoteBackgroundIfProtected(note) { | ||||||
| @@ -145,7 +139,7 @@ async function handleProtectedSession() { | |||||||
|     protectedSessionService.ensureDialogIsClosed(); |     protectedSessionService.ensureDialogIsClosed(); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function loadNoteToEditor(noteId) { | async function loadNoteDetail(noteId) { | ||||||
|     currentNote = await loadNote(noteId); |     currentNote = await loadNote(noteId); | ||||||
|  |  | ||||||
|     if (isNewNoteCreated) { |     if (isNewNoteCreated) { | ||||||
| @@ -182,7 +176,35 @@ async function loadNoteToEditor(noteId) { | |||||||
|     // after loading new note make sure editor is scrolled to the top |     // after loading new note make sure editor is scrolled to the top | ||||||
|     $noteDetailWrapper.scrollTop(0); |     $noteDetailWrapper.scrollTop(0); | ||||||
|  |  | ||||||
|     await loadLabelList(); |     const labels = await loadLabelList(); | ||||||
|  |  | ||||||
|  |     const hideChildrenOverview = labels.some(label => label.name === 'hideChildrenOverview'); | ||||||
|  |     await showChildrenOverview(hideChildrenOverview); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function showChildrenOverview(hideChildrenOverview) { | ||||||
|  |     if (hideChildrenOverview) { | ||||||
|  |         $childrenOverview.hide(); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const note = getCurrentNote(); | ||||||
|  |  | ||||||
|  |     $childrenOverview.empty(); | ||||||
|  |  | ||||||
|  |     const notePath = treeService.getCurrentNotePath(); | ||||||
|  |  | ||||||
|  |     for (const childBranch of await note.getChildBranches()) { | ||||||
|  |         const link = $('<a>', { | ||||||
|  |             href: 'javascript:', | ||||||
|  |             text: await treeUtils.getNoteTitle(childBranch.noteId, childBranch.parentNoteId) | ||||||
|  |         }).attr('action', 'note').attr('note-path', notePath + '/' + childBranch.noteId); | ||||||
|  |  | ||||||
|  |         const childEl = $('<div class="child-overview">').html(link); | ||||||
|  |         $childrenOverview.append(childEl); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $childrenOverview.show(); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function loadLabelList() { | async function loadLabelList() { | ||||||
| @@ -202,6 +224,8 @@ async function loadLabelList() { | |||||||
|     else { |     else { | ||||||
|         $labelList.hide(); |         $labelList.hide(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     return labels; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function loadNote(noteId) { | async function loadNote(noteId) { | ||||||
| @@ -245,8 +269,6 @@ setInterval(saveNoteIfChanged, 5000); | |||||||
| export default { | export default { | ||||||
|     reload, |     reload, | ||||||
|     switchToNote, |     switchToNote, | ||||||
|     updateNoteFromInputs, |  | ||||||
|     saveNoteToServer, |  | ||||||
|     setNoteBackgroundIfProtected, |     setNoteBackgroundIfProtected, | ||||||
|     loadNote, |     loadNote, | ||||||
|     getCurrentNote, |     getCurrentNote, | ||||||
| @@ -255,6 +277,7 @@ export default { | |||||||
|     newNoteCreated, |     newNoteCreated, | ||||||
|     focus, |     focus, | ||||||
|     loadLabelList, |     loadLabelList, | ||||||
|  |     saveNote, | ||||||
|     saveNoteIfChanged, |     saveNoteIfChanged, | ||||||
|     noteChanged |     noteChanged | ||||||
| }; | }; | ||||||
| @@ -1,4 +1,3 @@ | |||||||
| import utils from "./utils.js"; |  | ||||||
| import libraryLoader from "./library_loader.js"; | import libraryLoader from "./library_loader.js"; | ||||||
| import bundleService from "./bundle.js"; | import bundleService from "./bundle.js"; | ||||||
| import infoService from "./info.js"; | import infoService from "./info.js"; | ||||||
| @@ -11,15 +10,19 @@ const $noteDetailCode = $('#note-detail-code'); | |||||||
| const $executeScriptButton = $("#execute-script-button"); | const $executeScriptButton = $("#execute-script-button"); | ||||||
|  |  | ||||||
| async function show() { | async function show() { | ||||||
|     if (!codeEditor) { |     await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); | ||||||
|         await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); |  | ||||||
|  |  | ||||||
|  |     if (!codeEditor) { | ||||||
|         CodeMirror.keyMap.default["Shift-Tab"] = "indentLess"; |         CodeMirror.keyMap.default["Shift-Tab"] = "indentLess"; | ||||||
|         CodeMirror.keyMap.default["Tab"] = "indentMore"; |         CodeMirror.keyMap.default["Tab"] = "indentMore"; | ||||||
|  |  | ||||||
|  |         // these conflict with backward/forward navigation shortcuts | ||||||
|  |         delete CodeMirror.keyMap.default["Alt-Left"]; | ||||||
|  |         delete CodeMirror.keyMap.default["Alt-Right"]; | ||||||
|  |  | ||||||
|         CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js'; |         CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js'; | ||||||
|  |  | ||||||
|         codeEditor = CodeMirror($("#note-detail-code")[0], { |         codeEditor = CodeMirror($noteDetailCode[0], { | ||||||
|             value: "", |             value: "", | ||||||
|             viewportMargin: Infinity, |             viewportMargin: Infinity, | ||||||
|             indentUnit: 4, |             indentUnit: 4, | ||||||
| @@ -38,7 +41,7 @@ async function show() { | |||||||
|  |  | ||||||
|     const currentNote = noteDetailService.getCurrentNote(); |     const currentNote = noteDetailService.getCurrentNote(); | ||||||
|  |  | ||||||
|     // this needs to happen after the element is shown, otherwise the editor won't be refresheds |     // this needs to happen after the element is shown, otherwise the editor won't be refreshed | ||||||
|     codeEditor.setValue(currentNote.content); |     codeEditor.setValue(currentNote.content); | ||||||
|  |  | ||||||
|     const info = CodeMirror.findModeByMIME(currentNote.mime); |     const info = CodeMirror.findModeByMIME(currentNote.mime); | ||||||
| @@ -67,13 +70,13 @@ async function executeCurrentNote() { | |||||||
|         const currentNote = noteDetailService.getCurrentNote(); |         const currentNote = noteDetailService.getCurrentNote(); | ||||||
|  |  | ||||||
|         if (currentNote.mime.endsWith("env=frontend")) { |         if (currentNote.mime.endsWith("env=frontend")) { | ||||||
|             const bundle = await server.get('script/bundle/' + getCurrentNoteId()); |             const bundle = await server.get('script/bundle/' + noteDetailService.getCurrentNoteId()); | ||||||
|  |  | ||||||
|             bundleService.executeBundle(bundle); |             bundleService.executeBundle(bundle); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (currentNote.mime.endsWith("env=backend")) { |         if (currentNote.mime.endsWith("env=backend")) { | ||||||
|             await server.post('script/run/' + getCurrentNoteId()); |             await server.post('script/run/' + noteDetailService.getCurrentNoteId()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         infoService.showMessage("Note executed"); |         infoService.showMessage("Note executed"); | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import treeService from './tree.js'; | import treeService from './tree.js'; | ||||||
| import noteDetail from './note_detail.js'; | import noteDetailService from './note_detail.js'; | ||||||
| import server from './server.js'; | import server from './server.js'; | ||||||
| import infoService from "./info.js"; | import infoService from "./info.js"; | ||||||
|  |  | ||||||
| @@ -84,13 +84,13 @@ function NoteTypeModel() { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     async function save() { |     async function save() { | ||||||
|         const note = noteDetail.getCurrentNote(); |         const note = noteDetailService.getCurrentNote(); | ||||||
|  |  | ||||||
|         await server.put('notes/' + note.noteId |         await server.put('notes/' + note.noteId | ||||||
|             + '/type/' + encodeURIComponent(self.type()) |             + '/type/' + encodeURIComponent(self.type()) | ||||||
|             + '/mime/' + encodeURIComponent(self.mime())); |             + '/mime/' + encodeURIComponent(self.mime())); | ||||||
|  |  | ||||||
|         await noteDetail.reload(); |         await noteDetailService.reload(); | ||||||
|  |  | ||||||
|         // for the note icon to be updated in the tree |         // for the note icon to be updated in the tree | ||||||
|         await treeService.reload(); |         await treeService.reload(); | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import treeService from './tree.js'; | import treeService from './tree.js'; | ||||||
| import noteDetail from './note_detail.js'; | import noteDetailService from './note_detail.js'; | ||||||
| import utils from './utils.js'; | import utils from './utils.js'; | ||||||
| import server from './server.js'; | import server from './server.js'; | ||||||
| import protectedSessionHolder from './protected_session_holder.js'; | import protectedSessionHolder from './protected_session_holder.js'; | ||||||
| @@ -57,7 +57,7 @@ async function setupProtectedSession() { | |||||||
|  |  | ||||||
|     $dialog.dialog("close"); |     $dialog.dialog("close"); | ||||||
|  |  | ||||||
|     noteDetail.reload(); |     noteDetailService.reload(); | ||||||
|     treeService.reload(); |     treeService.reload(); | ||||||
|  |  | ||||||
|     if (protectedSessionDeferred !== null) { |     if (protectedSessionDeferred !== null) { | ||||||
| @@ -90,33 +90,27 @@ async function enterProtectedSession(password) { | |||||||
| async function protectNoteAndSendToServer() { | async function protectNoteAndSendToServer() { | ||||||
|     await ensureProtectedSession(true, true); |     await ensureProtectedSession(true, true); | ||||||
|  |  | ||||||
|     const note = noteDetail.getCurrentNote(); |     const note = noteDetailService.getCurrentNote(); | ||||||
|  |  | ||||||
|     noteDetail.updateNoteFromInputs(note); |  | ||||||
|  |  | ||||||
|     note.isProtected = true; |     note.isProtected = true; | ||||||
|  |  | ||||||
|     await noteDetail.saveNoteToServer(note); |     await noteDetailService.saveNote(note); | ||||||
|  |  | ||||||
|     treeService.setProtected(note.noteId, note.isProtected); |     treeService.setProtected(note.noteId, note.isProtected); | ||||||
|  |  | ||||||
|     noteDetail.setNoteBackgroundIfProtected(note); |     noteDetailService.setNoteBackgroundIfProtected(note); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function unprotectNoteAndSendToServer() { | async function unprotectNoteAndSendToServer() { | ||||||
|     await ensureProtectedSession(true, true); |     await ensureProtectedSession(true, true); | ||||||
|  |  | ||||||
|     const note = noteDetail.getCurrentNote(); |     const note = noteDetailService.getCurrentNote(); | ||||||
|  |  | ||||||
|     noteDetail.updateNoteFromInputs(note); |  | ||||||
|  |  | ||||||
|     note.isProtected = false; |     note.isProtected = false; | ||||||
|  |  | ||||||
|     await noteDetail.saveNoteToServer(note); |     await noteDetailService.saveNote(note); | ||||||
|  |  | ||||||
|     treeService.setProtected(note.noteId, note.isProtected); |     treeService.setProtected(note.noteId, note.isProtected); | ||||||
|  |  | ||||||
|     noteDetail.setNoteBackgroundIfProtected(note); |     noteDetailService.setNoteBackgroundIfProtected(note); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function protectBranch(noteId, protect) { | async function protectBranch(noteId, protect) { | ||||||
| @@ -127,7 +121,7 @@ async function protectBranch(noteId, protect) { | |||||||
|     infoService.showMessage("Request to un/protect sub tree has finished successfully"); |     infoService.showMessage("Request to un/protect sub tree has finished successfully"); | ||||||
|  |  | ||||||
|     treeService.reload(); |     treeService.reload(); | ||||||
|     noteDetail.reload(); |     noteDetailService.reload(); | ||||||
| } | } | ||||||
|  |  | ||||||
| $passwordForm.submit(() => { | $passwordForm.submit(() => { | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import treeService from './tree.js'; | import treeService from './tree.js'; | ||||||
| import server from './server.js'; | import server from './server.js'; | ||||||
| import utils from './utils.js'; | import utils from './utils.js'; | ||||||
|  | import infoService from './info.js'; | ||||||
|  |  | ||||||
| function ScriptApi(startNote, currentNote) { | function ScriptApi(startNote, currentNote) { | ||||||
|     const $pluginButtons = $("#plugin-buttons"); |     const $pluginButtons = $("#plugin-buttons"); | ||||||
| @@ -54,7 +55,11 @@ function ScriptApi(startNote, currentNote) { | |||||||
|         activateNote, |         activateNote, | ||||||
|         getInstanceName: () => window.glob.instanceName, |         getInstanceName: () => window.glob.instanceName, | ||||||
|         runOnServer, |         runOnServer, | ||||||
|         formatDateISO: utils.formatDateISO |         formatDateISO: utils.formatDateISO, | ||||||
|  |         parseDate: utils.parseDate, | ||||||
|  |         showMessage: infoService.showMessage, | ||||||
|  |         showError: infoService.showError, | ||||||
|  |         reloadTree: treeService.reload | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -85,19 +85,17 @@ async function ajax(url, method, data) { | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| setTimeout(() => { | if (utils.isElectron()) { | ||||||
|     if (utils.isElectron()) { |     const ipc = require('electron').ipcRenderer; | ||||||
|         const ipc = require('electron').ipcRenderer; |  | ||||||
|  |  | ||||||
|         ipc.on('server-response', (event, arg) => { |     ipc.on('server-response', (event, arg) => { | ||||||
|             console.log(utils.now(), "Response #" + arg.requestId + ": " + arg.statusCode); |         console.log(utils.now(), "Response #" + arg.requestId + ": " + arg.statusCode); | ||||||
|  |  | ||||||
|             reqResolves[arg.requestId](arg.body); |         reqResolves[arg.requestId](arg.body); | ||||||
|  |  | ||||||
|             delete reqResolves[arg.requestId]; |         delete reqResolves[arg.requestId]; | ||||||
|         }); |     }); | ||||||
|     } | } | ||||||
| }, 100); |  | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     get, |     get, | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import utils from './utils.js'; | import server from './server.js'; | ||||||
| import infoService from "./info.js"; | import infoService from "./info.js"; | ||||||
|  |  | ||||||
| async function syncNow() { | async function syncNow() { | ||||||
| @@ -19,7 +19,7 @@ async function syncNow() { | |||||||
| $("#sync-now-button").click(syncNow); | $("#sync-now-button").click(syncNow); | ||||||
|  |  | ||||||
| async function forceNoteSync(noteId) { | async function forceNoteSync(noteId) { | ||||||
|     const result = await server.post('sync/force-note-sync/' + noteId); |     await server.post('sync/force-note-sync/' + noteId); | ||||||
|  |  | ||||||
|     infoService.showMessage("Note added to sync queue."); |     infoService.showMessage("Note added to sync queue."); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -293,7 +293,7 @@ function initFancyTree(branch) { | |||||||
|         keyboard: false, // we takover keyboard handling in the hotkeys plugin |         keyboard: false, // we takover keyboard handling in the hotkeys plugin | ||||||
|         extensions: ["hotkeys", "filter", "dnd", "clones"], |         extensions: ["hotkeys", "filter", "dnd", "clones"], | ||||||
|         source: branch, |         source: branch, | ||||||
|         scrollParent: $("#tree"), |         scrollParent: $tree, | ||||||
|         click: (event, data) => { |         click: (event, data) => { | ||||||
|             const targetType = data.targetType; |             const targetType = data.targetType; | ||||||
|             const node = data.node; |             const node = data.node; | ||||||
|   | |||||||
							
								
								
									
										799
									
								
								src/public/libraries/jquery.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										799
									
								
								src/public/libraries/jquery.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										7
									
								
								src/public/libraries/jquery.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								src/public/libraries/jquery.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -5,9 +5,9 @@ | |||||||
|     display: grid; |     display: grid; | ||||||
|     grid-template-areas: "header header" |     grid-template-areas: "header header" | ||||||
|                          "tree-actions title" |                          "tree-actions title" | ||||||
|                          "search note-content" |                          "search note-detail" | ||||||
|                          "tree note-content" |                          "tree note-detail" | ||||||
|                          "parent-list note-content" |                          "parent-list note-detail" | ||||||
|                          "parent-list label-list"; |                          "parent-list label-list"; | ||||||
|     grid-template-columns: 2fr 5fr; |     grid-template-columns: 2fr 5fr; | ||||||
|     grid-template-rows: auto |     grid-template-rows: auto | ||||||
| @@ -36,7 +36,7 @@ | |||||||
|     border: 0 !important; |     border: 0 !important; | ||||||
|     box-shadow: none !important; |     box-shadow: none !important; | ||||||
|     /* This is because with empty content height of editor is 0 and it's impossible to click into it */ |     /* This is because with empty content height of editor is 0 and it's impossible to click into it */ | ||||||
|     min-height: 400px; |     min-height: 200px; | ||||||
|     overflow: auto; |     overflow: auto; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -249,7 +249,7 @@ div.ui-tooltip { | |||||||
| } | } | ||||||
|  |  | ||||||
| #note-detail-code { | #note-detail-code { | ||||||
|     height: 100%; |     min-height: 200px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .CodeMirror { | .CodeMirror { | ||||||
| @@ -288,4 +288,36 @@ div.ui-tooltip { | |||||||
| #file-table th, #file-table td { | #file-table th, #file-table td { | ||||||
|     padding: 10px; |     padding: 10px; | ||||||
|     font-size: large; |     font-size: large; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #children-overview { | ||||||
|  |     flex-grow: 1000; | ||||||
|  |     flex-shrink: 1000; | ||||||
|  |     flex-basis: 0px; | ||||||
|  |     display: flex; | ||||||
|  |     flex-wrap: wrap; | ||||||
|  |     align-content: flex-start; | ||||||
|  |     height: 100px; | ||||||
|  |     overflow: auto; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .child-overview { | ||||||
|  |     font-weight: bold; | ||||||
|  |     font-size: large; | ||||||
|  |     padding: 10px; | ||||||
|  |     background: #f4f4f4; | ||||||
|  |     width: 150px; | ||||||
|  |     height: 90px; | ||||||
|  |     line-height: 2em; | ||||||
|  |     margin-right: 20px; | ||||||
|  |     border-radius: 15px; | ||||||
|  |     overflow: hidden; | ||||||
|  |     text-align: center; | ||||||
|  |     margin-top: 15px; | ||||||
|  |     text-overflow: ellipsis; | ||||||
|  |     word-wrap: break-word; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .child-overview a { | ||||||
|  |     color: #444; | ||||||
| } | } | ||||||
| @@ -13,7 +13,68 @@ async function exportNote(req, res) { | |||||||
|  |  | ||||||
|     const pack = tar.pack(); |     const pack = tar.pack(); | ||||||
|  |  | ||||||
|     const name = await exportNoteInner(branchId, '', pack); |     const exportedNoteIds = []; | ||||||
|  |     const name = await exportNoteInner(branchId, ''); | ||||||
|  |  | ||||||
|  |     async function exportNoteInner(branchId, directory) { | ||||||
|  |         const branch = await repository.getBranch(branchId); | ||||||
|  |         const note = await branch.getNote(); | ||||||
|  |         const childFileName = directory + sanitize(note.title); | ||||||
|  |  | ||||||
|  |         if (exportedNoteIds.includes(note.noteId)) { | ||||||
|  |             saveMetadataFile(childFileName, { | ||||||
|  |                 version: 1, | ||||||
|  |                 clone: true, | ||||||
|  |                 noteId: note.noteId, | ||||||
|  |                 prefix: branch.prefix | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const metadata = { | ||||||
|  |             version: 1, | ||||||
|  |             clone: false, | ||||||
|  |             noteId: note.noteId, | ||||||
|  |             title: note.title, | ||||||
|  |             prefix: branch.prefix, | ||||||
|  |             type: note.type, | ||||||
|  |             mime: note.mime, | ||||||
|  |             labels: (await note.getLabels()).map(label => { | ||||||
|  |                 return { | ||||||
|  |                     name: label.name, | ||||||
|  |                     value: label.value | ||||||
|  |                 }; | ||||||
|  |             }) | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         if (metadata.labels.find(label => label.name === 'excludeFromExport')) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         saveMetadataFile(childFileName, metadata); | ||||||
|  |         saveDataFile(childFileName, note); | ||||||
|  |  | ||||||
|  |         exportedNoteIds.push(note.noteId); | ||||||
|  |  | ||||||
|  |         for (const child of await note.getChildBranches()) { | ||||||
|  |             await exportNoteInner(child.branchId, childFileName + "/"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return childFileName; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function saveDataFile(childFileName, note) { | ||||||
|  |         const content = note.type === 'text' ? html.prettyPrint(note.content, {indent_size: 2}) : note.content; | ||||||
|  |  | ||||||
|  |         pack.entry({name: childFileName + ".dat", size: content.length}, content); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function saveMetadataFile(childFileName, metadata) { | ||||||
|  |         const metadataJson = JSON.stringify(metadata, null, '\t'); | ||||||
|  |  | ||||||
|  |         pack.entry({name: childFileName + ".meta", size: metadataJson.length}, metadataJson); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     pack.finalize(); |     pack.finalize(); | ||||||
|  |  | ||||||
| @@ -23,51 +84,6 @@ async function exportNote(req, res) { | |||||||
|     pack.pipe(res); |     pack.pipe(res); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function exportNoteInner(branchId, directory, pack) { |  | ||||||
|     const branch = await repository.getBranch(branchId); |  | ||||||
|     const note = await branch.getNote(); |  | ||||||
|  |  | ||||||
|     if (note.isProtected) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const metadata = await getMetadata(note); |  | ||||||
|  |  | ||||||
|     if (metadata.labels.find(label => label.name === 'excludeFromExport')) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const metadataJson = JSON.stringify(metadata, null, '\t'); |  | ||||||
|     const childFileName = directory + sanitize(note.title); |  | ||||||
|  |  | ||||||
|     pack.entry({ name: childFileName + ".meta", size: metadataJson.length }, metadataJson); |  | ||||||
|  |  | ||||||
|     const content = note.type === 'text' ? html.prettyPrint(note.content, {indent_size: 2}) : note.content; |  | ||||||
|  |  | ||||||
|     pack.entry({ name: childFileName + ".dat", size: content.length }, content); |  | ||||||
|  |  | ||||||
|     for (const child of await note.getChildBranches()) { |  | ||||||
|         await exportNoteInner(child.branchId, childFileName + "/", pack); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return childFileName; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getMetadata(note) { |  | ||||||
|     return { |  | ||||||
|         version: 1, |  | ||||||
|         title: note.title, |  | ||||||
|         type: note.type, |  | ||||||
|         mime: note.mime, |  | ||||||
|         labels: (await note.getLabels()).map(label => { |  | ||||||
|             return { |  | ||||||
|                 name: label.name, |  | ||||||
|                 value: label.value |  | ||||||
|             }; |  | ||||||
|         }) |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     exportNote |     exportNote | ||||||
| }; | }; | ||||||
| @@ -3,6 +3,7 @@ | |||||||
| const repository = require('../../services/repository'); | const repository = require('../../services/repository'); | ||||||
| const labelService = require('../../services/labels'); | const labelService = require('../../services/labels'); | ||||||
| const noteService = require('../../services/notes'); | const noteService = require('../../services/notes'); | ||||||
|  | const Branch = require('../../entities/branch'); | ||||||
| const tar = require('tar-stream'); | const tar = require('tar-stream'); | ||||||
| const stream = require('stream'); | const stream = require('stream'); | ||||||
| const path = require('path'); | const path = require('path'); | ||||||
| @@ -31,7 +32,7 @@ async function parseImportFile(file) { | |||||||
|     const extract = tar.extract(); |     const extract = tar.extract(); | ||||||
|  |  | ||||||
|     extract.on('entry', function(header, stream, next) { |     extract.on('entry', function(header, stream, next) { | ||||||
|         let {name, key} = getFileName(header.name); |         const {name, key} = getFileName(header.name); | ||||||
|  |  | ||||||
|         let file = fileMap[name]; |         let file = fileMap[name]; | ||||||
|  |  | ||||||
| @@ -97,30 +98,46 @@ async function importTar(req) { | |||||||
|  |  | ||||||
|     const files = await parseImportFile(file); |     const files = await parseImportFile(file); | ||||||
|  |  | ||||||
|     await importNotes(files, parentNoteId); |     // maps from original noteId (in tar file) to newly generated noteId | ||||||
|  |     const noteIdMap = {}; | ||||||
|  |  | ||||||
|  |     await importNotes(files, parentNoteId, noteIdMap); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function importNotes(files, parentNoteId) { | async function importNotes(files, parentNoteId, noteIdMap) { | ||||||
|     for (const file of files) { |     for (const file of files) { | ||||||
|         if (file.meta.version !== 1) { |         if (file.meta.version !== 1) { | ||||||
|             throw new Error("Can't read meta data version " + file.meta.version); |             throw new Error("Can't read meta data version " + file.meta.version); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (file.meta.clone) { | ||||||
|  |             await new Branch({ | ||||||
|  |                 parentNoteId: parentNoteId, | ||||||
|  |                 noteId: noteIdMap[file.meta.noteId], | ||||||
|  |                 prefix: file.meta.prefix | ||||||
|  |             }).save(); | ||||||
|  |  | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (file.meta.type !== 'file') { |         if (file.meta.type !== 'file') { | ||||||
|             file.data = file.data.toString("UTF-8"); |             file.data = file.data.toString("UTF-8"); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const {note} = await noteService.createNote(parentNoteId, file.meta.title, file.data, { |         const {note} = await noteService.createNote(parentNoteId, file.meta.title, file.data, { | ||||||
|             type: file.meta.type, |             type: file.meta.type, | ||||||
|             mime: file.meta.mime |             mime: file.meta.mime, | ||||||
|  |             prefix: file.meta.prefix | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         noteIdMap[file.meta.noteId] = note.noteId; | ||||||
|  |  | ||||||
|         for (const label of file.meta.labels) { |         for (const label of file.meta.labels) { | ||||||
|             await labelService.createLabel(note.noteId, label.name, label.value); |             await labelService.createLabel(note.noteId, label.name, label.value); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (file.children.length > 0) { |         if (file.children.length > 0) { | ||||||
|             await importNotes(file.children, note.noteId); |             await importNotes(file.children, note.noteId, noteIdMap); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ const appInfo = require('../../services/app_info'); | |||||||
| async function getMigrationInfo() { | async function getMigrationInfo() { | ||||||
|     return { |     return { | ||||||
|         dbVersion: parseInt(await optionService.getOption('dbVersion')), |         dbVersion: parseInt(await optionService.getOption('dbVersion')), | ||||||
|         app_dbVersion: appInfo.dbVersion |         appDbVersion: appInfo.dbVersion | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -36,9 +36,9 @@ async function uploadImage(req) { | |||||||
|         return [400, "Unknown image type: " + file.mimetype]; |         return [400, "Unknown image type: " + file.mimetype]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const parentNoteId = await dateNoteService.getDateNoteId(req.headers['x-local-date']); |     const parentNote = await dateNoteService.getDateNote(req.headers['x-local-date']); | ||||||
|  |  | ||||||
|     const {note} = await noteService.createNewNote(parentNoteId, { |     const {note} = await noteService.createNewNote(parentNote.noteId, { | ||||||
|         title: "Sender image", |         title: "Sender image", | ||||||
|         content: "", |         content: "", | ||||||
|         target: 'into', |         target: 'into', | ||||||
| @@ -57,9 +57,9 @@ async function uploadImage(req) { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function saveNote(req) { | async function saveNote(req) { | ||||||
|     const parentNoteId = await dateNoteService.getDateNoteId(req.headers['x-local-date']); |     const parentNote = await dateNoteService.getDateNote(req.headers['x-local-date']); | ||||||
|  |  | ||||||
|     await noteService.createNewNote(parentNoteId, { |     await noteService.createNewNote(parentNote.noteId, { | ||||||
|         title: req.body.title, |         title: req.body.title, | ||||||
|         content: req.body.content, |         content: req.body.content, | ||||||
|         target: 'into', |         target: 'into', | ||||||
|   | |||||||
| @@ -10,8 +10,8 @@ const log = require('../../services/log'); | |||||||
|  |  | ||||||
| async function checkSync() { | async function checkSync() { | ||||||
|     return { |     return { | ||||||
|         'hashes': await contentHashService.getHashes(), |         hashes: await contentHashService.getHashes(), | ||||||
|         'max_sync_id': await sql.getValue('SELECT MAX(id) FROM sync') |         maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync') | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -55,129 +55,21 @@ async function forceNoteSync(req) { | |||||||
|     syncService.sync(); |     syncService.sync(); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function getChanged() { | async function getChanged(req) { | ||||||
|     const lastSyncId = parseInt(req.query.lastSyncId); |     const lastSyncId = parseInt(req.query.lastSyncId); | ||||||
|  |  | ||||||
|     return await sql.getRows("SELECT * FROM sync WHERE id > ?", [lastSyncId]); |     const syncs = await sql.getRows("SELECT * FROM sync WHERE id > ? LIMIT 1000", [lastSyncId]); | ||||||
|  |  | ||||||
|  |     return await syncService.getSyncRecords(syncs); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function getNote(req) { | async function update(req) { | ||||||
|     const noteId = req.params.noteId; |     const sourceId = req.body.sourceId; | ||||||
|     const entity = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); |     const entities = req.body.entities; | ||||||
|  |  | ||||||
|     syncService.serializeNoteContentBuffer(entity); |     for (const {sync, entity} of entities) { | ||||||
|  |         await syncUpdateService.updateEntity(sync.entityName, entity, sourceId); | ||||||
|     return { |  | ||||||
|         entity: entity |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getBranch(req) { |  | ||||||
|     const branchId = req.params.branchId; |  | ||||||
|  |  | ||||||
|     return await sql.getRow("SELECT * FROM branches WHERE branchId = ?", [branchId]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getNoteRevision(req) { |  | ||||||
|     const noteRevisionId = req.params.noteRevisionId; |  | ||||||
|  |  | ||||||
|     return await sql.getRow("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [noteRevisionId]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getOption(req) { |  | ||||||
|     const name = req.params.name; |  | ||||||
|     const opt = await sql.getRow("SELECT * FROM options WHERE name = ?", [name]); |  | ||||||
|  |  | ||||||
|     if (!opt.isSynced) { |  | ||||||
|         return [400, "This option can't be synced."]; |  | ||||||
|     } |     } | ||||||
|     else { |  | ||||||
|         return opt; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getNoteReordering(req) { |  | ||||||
|     const parentNoteId = req.params.parentNoteId; |  | ||||||
|  |  | ||||||
|     return { |  | ||||||
|         parentNoteId: parentNoteId, |  | ||||||
|         ordering: await sql.getMap("SELECT branchId, notePosition FROM branches WHERE parentNoteId = ? AND isDeleted = 0", [parentNoteId]) |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getRecentNote(req) { |  | ||||||
|     const branchId = req.params.branchId; |  | ||||||
|  |  | ||||||
|     return await sql.getRow("SELECT * FROM recent_notes WHERE branchId = ?", [branchId]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getImage(req) { |  | ||||||
|     const imageId = req.params.imageId; |  | ||||||
|     const entity = await sql.getRow("SELECT * FROM images WHERE imageId = ?", [imageId]); |  | ||||||
|  |  | ||||||
|     if (entity && entity.data !== null) { |  | ||||||
|         entity.data = entity.data.toString('base64'); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return entity; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getNoteImage(req) { |  | ||||||
|     const noteImageId = req.params.noteImageId; |  | ||||||
|  |  | ||||||
|     return await sql.getRow("SELECT * FROM note_images WHERE noteImageId = ?", [noteImageId]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getLabel(req) { |  | ||||||
|     const labelId = req.params.labelId; |  | ||||||
|  |  | ||||||
|     return await sql.getRow("SELECT * FROM labels WHERE labelId = ?", [labelId]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getApiToken(req) { |  | ||||||
|     const apiTokenId = req.params.apiTokenId; |  | ||||||
|  |  | ||||||
|     return await sql.getRow("SELECT * FROM api_tokens WHERE apiTokenId = ?", [apiTokenId]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function updateNote(req) { |  | ||||||
|     await syncUpdateService.updateNote(req.body.entity, req.body.sourceId); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function updateBranch(req) { |  | ||||||
|     await syncUpdateService.updateBranch(req.body.entity, req.body.sourceId); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function updateNoteRevision(req) { |  | ||||||
|     await syncUpdateService.updateNoteRevision(req.body.entity, req.body.sourceId); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function updateNoteReordering(req) { |  | ||||||
|     await syncUpdateService.updateNoteReordering(req.body.entity, req.body.sourceId); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function updateOption(req) { |  | ||||||
|     await syncUpdateService.updateOptions(req.body.entity, req.body.sourceId); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function updateRecentNote(req) { |  | ||||||
|     await syncUpdateService.updateRecentNotes(req.body.entity, req.body.sourceId); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function updateImage(req) { |  | ||||||
|     await syncUpdateService.updateImage(req.body.entity, req.body.sourceId); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function updateNoteImage(req) { |  | ||||||
|     await syncUpdateService.updateNoteImage(req.body.entity, req.body.sourceId); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function updateLabel(req) { |  | ||||||
|     await syncUpdateService.updateLabel(req.body.entity, req.body.sourceId); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function updateApiToken(req) { |  | ||||||
|     await syncUpdateService.updateApiToken(req.body.entity, req.body.sourceId); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
| @@ -187,24 +79,5 @@ module.exports = { | |||||||
|     forceFullSync, |     forceFullSync, | ||||||
|     forceNoteSync, |     forceNoteSync, | ||||||
|     getChanged, |     getChanged, | ||||||
|     getNote, |     update | ||||||
|     getBranch, |  | ||||||
|     getImage, |  | ||||||
|     getNoteImage, |  | ||||||
|     getNoteReordering, |  | ||||||
|     getNoteRevision, |  | ||||||
|     getRecentNote, |  | ||||||
|     getOption, |  | ||||||
|     getLabel, |  | ||||||
|     getApiToken, |  | ||||||
|     updateNote, |  | ||||||
|     updateBranch, |  | ||||||
|     updateImage, |  | ||||||
|     updateNoteImage, |  | ||||||
|     updateNoteReordering, |  | ||||||
|     updateNoteRevision, |  | ||||||
|     updateRecentNote, |  | ||||||
|     updateOption, |  | ||||||
|     updateLabel, |  | ||||||
|     updateApiToken |  | ||||||
| }; | }; | ||||||
| @@ -19,6 +19,7 @@ function init(app) { | |||||||
|  |  | ||||||
|         res.status = function(statusCode) { |         res.status = function(statusCode) { | ||||||
|             res.statusCode = statusCode; |             res.statusCode = statusCode; | ||||||
|  |             return res; | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         res.send = function(obj) { |         res.send = function(obj) { | ||||||
|   | |||||||
| @@ -40,22 +40,22 @@ const cls = require('../services/cls'); | |||||||
| const sql = require('../services/sql'); | const sql = require('../services/sql'); | ||||||
| const protectedSessionService = require('../services/protected_session'); | const protectedSessionService = require('../services/protected_session'); | ||||||
|  |  | ||||||
| function apiResultHandler(res, result) { | function apiResultHandler(req, res, result) { | ||||||
|     // if it's an array and first element is integer then we consider this to be [statusCode, response] format |     // if it's an array and first element is integer then we consider this to be [statusCode, response] format | ||||||
|     if (Array.isArray(result) && result.length > 0 && Number.isInteger(result[0])) { |     if (Array.isArray(result) && result.length > 0 && Number.isInteger(result[0])) { | ||||||
|         const [statusCode, response] = result; |         const [statusCode, response] = result; | ||||||
|  |  | ||||||
|         res.status(statusCode).send(response); |         res.status(statusCode).send(response); | ||||||
|  |  | ||||||
|         if (statusCode !== 200) { |         if (statusCode !== 200 && statusCode !== 201 && statusCode !== 204) { | ||||||
|             log.info(`${method} ${path} returned ${statusCode} with response ${JSON.stringify(response)}`); |             log.info(`${req.method} ${req.originalUrl} returned ${statusCode} with response ${JSON.stringify(response)}`); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     else if (result === undefined) { |     else if (result === undefined) { | ||||||
|         res.status(204).send(); |         res.status(204).send(); | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|         res.status(200).send(result); |         res.send(result); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -70,13 +70,13 @@ function route(method, path, middleware, routeHandler, resultHandler) { | |||||||
|                 cls.namespace.set('sourceId', req.headers.source_id); |                 cls.namespace.set('sourceId', req.headers.source_id); | ||||||
|                 protectedSessionService.setProtectedSessionId(req); |                 protectedSessionService.setProtectedSessionId(req); | ||||||
|  |  | ||||||
|                 return await sql.doInTransaction(async () => { |                 return await sql.transactional(async () => { | ||||||
|                     return await routeHandler(req, res, next); |                     return await routeHandler(req, res, next); | ||||||
|                 }); |                 }); | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|             if (resultHandler) { |             if (resultHandler) { | ||||||
|                 resultHandler(res, result); |                 resultHandler(req, res, result); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         catch (e) { |         catch (e) { | ||||||
| @@ -147,25 +147,7 @@ function register(app) { | |||||||
|     apiRoute(POST, '/api/sync/force-full-sync', syncApiRoute.forceFullSync); |     apiRoute(POST, '/api/sync/force-full-sync', syncApiRoute.forceFullSync); | ||||||
|     apiRoute(POST, '/api/sync/force-note-sync/:noteId', syncApiRoute.forceNoteSync); |     apiRoute(POST, '/api/sync/force-note-sync/:noteId', syncApiRoute.forceNoteSync); | ||||||
|     apiRoute(GET, '/api/sync/changed', syncApiRoute.getChanged); |     apiRoute(GET, '/api/sync/changed', syncApiRoute.getChanged); | ||||||
|     apiRoute(GET, '/api/sync/notes/:noteId', syncApiRoute.getNote); |     apiRoute(PUT, '/api/sync/update', syncApiRoute.update); | ||||||
|     apiRoute(GET, '/api/sync/branches/:branchId', syncApiRoute.getBranch); |  | ||||||
|     apiRoute(GET, '/api/sync/note_revisions/:noteRevisionId', syncApiRoute.getNoteRevision); |  | ||||||
|     apiRoute(GET, '/api/sync/options/:name', syncApiRoute.getOption); |  | ||||||
|     apiRoute(GET, '/api/sync/note_reordering/:parentNoteId', syncApiRoute.getNoteReordering); |  | ||||||
|     apiRoute(GET, '/api/sync/recent_notes/:branchId', syncApiRoute.getRecentNote); |  | ||||||
|     apiRoute(GET, '/api/sync/images/:imageId', syncApiRoute.getImage); |  | ||||||
|     apiRoute(GET, '/api/sync/note_images/:noteImageId', syncApiRoute.getNoteImage); |  | ||||||
|     apiRoute(GET, '/api/sync/labels/:labelId', syncApiRoute.getLabel); |  | ||||||
|     apiRoute(GET, '/api/sync/api_tokens/:apiTokenId', syncApiRoute.getApiToken); |  | ||||||
|     apiRoute(PUT, '/api/sync/notes', syncApiRoute.updateNote); |  | ||||||
|     apiRoute(PUT, '/api/sync/note_revisions', syncApiRoute.updateNoteRevision); |  | ||||||
|     apiRoute(PUT, '/api/sync/note_reordering', syncApiRoute.updateNoteReordering); |  | ||||||
|     apiRoute(PUT, '/api/sync/options', syncApiRoute.updateOption); |  | ||||||
|     apiRoute(PUT, '/api/sync/recent_notes', syncApiRoute.updateRecentNote); |  | ||||||
|     apiRoute(PUT, '/api/sync/images', syncApiRoute.updateImage); |  | ||||||
|     apiRoute(PUT, '/api/sync/note_images', syncApiRoute.updateNoteImage); |  | ||||||
|     apiRoute(PUT, '/api/sync/labels', syncApiRoute.updateLabel); |  | ||||||
|     apiRoute(PUT, '/api/sync/api_tokens', syncApiRoute.updateApiToken); |  | ||||||
|  |  | ||||||
|     apiRoute(GET, '/api/event-log', eventLogRoute.getEventLog); |     apiRoute(GET, '/api/event-log', eventLogRoute.getEventLog); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| const build = require('./build'); | const build = require('./build'); | ||||||
| const packageJson = require('../../package'); | const packageJson = require('../../package'); | ||||||
|  |  | ||||||
| const APP_DB_VERSION = 86; | const APP_DB_VERSION = 88; | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     appVersion: packageJson.version, |     appVersion: packageJson.version, | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| module.exports = { buildDate:"2018-01-17T23:59:03-05:00", buildRevision: "651a9fb3272c85d287c16d5a4978464fb7d2490d" }; | module.exports = { buildDate:"2018-04-11T00:10:33-04:00", buildRevision: "a4eafb934ff3cdb46dbc138b1b02850872948699" }; | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ async function changePassword(currentPassword, newPassword) { | |||||||
|     const newPasswordVerificationKey = utils.toBase64(await myScryptService.getVerificationHash(newPassword)); |     const newPasswordVerificationKey = utils.toBase64(await myScryptService.getVerificationHash(newPassword)); | ||||||
|     const decryptedDataKey = await passwordEncryptionService.getDataKey(currentPassword); |     const decryptedDataKey = await passwordEncryptionService.getDataKey(currentPassword); | ||||||
|  |  | ||||||
|     await sql.doInTransaction(async () => { |     await sql.transactional(async () => { | ||||||
|         await passwordEncryptionService.setDataKey(newPassword, decryptedDataKey); |         await passwordEncryptionService.setDataKey(newPassword, decryptedDataKey); | ||||||
|  |  | ||||||
|         await optionService.setOption('passwordVerificationHash', newPasswordVerificationKey); |         await optionService.setOption('passwordVerificationHash', newPasswordVerificationKey); | ||||||
|   | |||||||
| @@ -1,6 +1,10 @@ | |||||||
|  | "use strict"; | ||||||
|  |  | ||||||
| const sql = require('./sql'); | const sql = require('./sql'); | ||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
| const log = require('./log'); | const log = require('./log'); | ||||||
|  | const eventLogService = require('./event_log'); | ||||||
|  | const messagingService = require('./messaging'); | ||||||
|  |  | ||||||
| function getHash(rows) { | function getHash(rows) { | ||||||
|     let hash = ''; |     let hash = ''; | ||||||
| @@ -121,6 +125,29 @@ async function getHashes() { | |||||||
|     return hashes; |     return hashes; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function checkContentHashes(otherHashes) { | ||||||
|  |     const hashes = await getHashes(); | ||||||
|  |     let allChecksPassed = true; | ||||||
|  |  | ||||||
|  |     for (const key in hashes) { | ||||||
|  |         if (hashes[key] !== otherHashes[key]) { | ||||||
|  |             allChecksPassed = false; | ||||||
|  |  | ||||||
|  |             await eventLogService.addEvent(`Content hash check for ${key} FAILED. Local is ${hashes[key]}, remote is ${resp.hashes[key]}`); | ||||||
|  |  | ||||||
|  |             if (key !== 'recent_notes') { | ||||||
|  |                 // let's not get alarmed about recent notes which get updated often and can cause failures in race conditions | ||||||
|  |                 await messagingService.sendMessageToAllClients({type: 'sync-hash-check-failed'}); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (allChecksPassed) { | ||||||
|  |         log.info("Content hash checks PASSED"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     getHashes |     getHashes, | ||||||
|  |     checkContentHashes | ||||||
| }; | }; | ||||||
| @@ -4,6 +4,7 @@ const sql = require('./sql'); | |||||||
| const noteService = require('./notes'); | const noteService = require('./notes'); | ||||||
| const labelService = require('./labels'); | const labelService = require('./labels'); | ||||||
| const dateUtils = require('./date_utils'); | const dateUtils = require('./date_utils'); | ||||||
|  | const repository = require('./repository'); | ||||||
|  |  | ||||||
| const CALENDAR_ROOT_LABEL = 'calendarRoot'; | const CALENDAR_ROOT_LABEL = 'calendarRoot'; | ||||||
| const YEAR_LABEL = 'yearNote'; | const YEAR_LABEL = 'yearNote'; | ||||||
| @@ -14,117 +15,112 @@ const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Satur | |||||||
| const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December']; | const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December']; | ||||||
|  |  | ||||||
| async function createNote(parentNoteId, noteTitle, noteText) { | async function createNote(parentNoteId, noteTitle, noteText) { | ||||||
|     const {note} = await noteService.createNewNote(parentNoteId, { |     return (await noteService.createNewNote(parentNoteId, { | ||||||
|         title: noteTitle, |         title: noteTitle, | ||||||
|         content: noteText, |         content: noteText, | ||||||
|         target: 'into', |         target: 'into', | ||||||
|         isProtected: false |         isProtected: false | ||||||
|     }); |     })).note; | ||||||
|  |  | ||||||
|     return note.noteId; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| async function getNoteStartingWith(parentNoteId, startsWith) { | async function getNoteStartingWith(parentNoteId, startsWith) { | ||||||
|     return await sql.getValue(`SELECT noteId FROM notes JOIN branches USING(noteId)  |     return await repository.getEntity(`SELECT notes.* FROM notes JOIN branches USING(noteId)  | ||||||
|                                     WHERE parentNoteId = ? AND title LIKE '${startsWith}%' |                                     WHERE parentNoteId = ? AND title LIKE '${startsWith}%' | ||||||
|                                     AND notes.isDeleted = 0 AND isProtected = 0  |                                     AND notes.isDeleted = 0 AND isProtected = 0  | ||||||
|                                     AND branches.isDeleted = 0`, [parentNoteId]); |                                     AND branches.isDeleted = 0`, [parentNoteId]); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function getRootCalendarNoteId() { | async function getRootCalendarNote() { | ||||||
|     let rootNoteId = await sql.getValue(`SELECT notes.noteId FROM notes JOIN labels USING(noteId)  |     let rootNote = await labelService.getNoteWithLabel(CALENDAR_ROOT_LABEL); | ||||||
|               WHERE labels.name = '${CALENDAR_ROOT_LABEL}' AND notes.isDeleted = 0`); |  | ||||||
|  |  | ||||||
|     if (!rootNoteId) { |     if (!rootNote) { | ||||||
|         const {rootNote} = await noteService.createNewNote('root', { |         rootNote = (await noteService.createNewNote('root', { | ||||||
|             title: 'Calendar', |             title: 'Calendar', | ||||||
|             target: 'into', |             target: 'into', | ||||||
|             isProtected: false |             isProtected: false | ||||||
|         }); |         })).note; | ||||||
|  |  | ||||||
|         const rootNoteId = rootNote.noteId; |         await labelService.createLabel(rootNote.noteId, CALENDAR_ROOT_LABEL); | ||||||
|  |  | ||||||
|         await labelService.createLabel(rootNoteId, CALENDAR_ROOT_LABEL); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return rootNoteId; |     return rootNote; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function getYearNoteId(dateTimeStr, rootNoteId) { | async function getYearNote(dateTimeStr, rootNote) { | ||||||
|     const yearStr = dateTimeStr.substr(0, 4); |     const yearStr = dateTimeStr.substr(0, 4); | ||||||
|  |  | ||||||
|     let yearNoteId = await labelService.getNoteIdWithLabel(YEAR_LABEL, yearStr); |     let yearNote = await labelService.getNoteWithLabel(YEAR_LABEL, yearStr); | ||||||
|  |  | ||||||
|     if (!yearNoteId) { |     if (!yearNote) { | ||||||
|         yearNoteId = await getNoteStartingWith(rootNoteId, yearStr); |         yearNote = await getNoteStartingWith(rootNote.noteId, yearStr); | ||||||
|  |  | ||||||
|         if (!yearNoteId) { |         if (!yearNote) { | ||||||
|             yearNoteId = await createNote(rootNoteId, yearStr); |             yearNote = await createNote(rootNote.noteId, yearStr); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         await labelService.createLabel(yearNoteId, YEAR_LABEL, yearStr); |         await labelService.createLabel(yearNote.noteId, YEAR_LABEL, yearStr); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return yearNoteId; |     return yearNote; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function getMonthNoteId(dateTimeStr, rootNoteId) { | async function getMonthNote(dateTimeStr, rootNote) { | ||||||
|     const monthStr = dateTimeStr.substr(0, 7); |     const monthStr = dateTimeStr.substr(0, 7); | ||||||
|     const monthNumber = dateTimeStr.substr(5, 2); |     const monthNumber = dateTimeStr.substr(5, 2); | ||||||
|  |  | ||||||
|     let monthNoteId = await labelService.getNoteIdWithLabel(MONTH_LABEL, monthStr); |     let monthNote = await labelService.getNoteWithLabel(MONTH_LABEL, monthStr); | ||||||
|  |  | ||||||
|     if (!monthNoteId) { |     if (!monthNote) { | ||||||
|         const yearNoteId = await getYearNoteId(dateTimeStr, rootNoteId); |         const yearNote = await getYearNote(dateTimeStr, rootNote); | ||||||
|  |  | ||||||
|         monthNoteId = await getNoteStartingWith(yearNoteId, monthNumber); |         monthNote = await getNoteStartingWith(yearNote.noteId, monthNumber); | ||||||
|  |  | ||||||
|         if (!monthNoteId) { |         if (!monthNote) { | ||||||
|             const dateObj = dateUtils.parseDate(dateTimeStr); |             const dateObj = dateUtils.parseDate(dateTimeStr); | ||||||
|  |  | ||||||
|             const noteTitle = monthNumber + " - " + MONTHS[dateObj.getMonth()]; |             const noteTitle = monthNumber + " - " + MONTHS[dateObj.getMonth()]; | ||||||
|  |  | ||||||
|             monthNoteId = await createNote(yearNoteId, noteTitle); |             monthNote = await createNote(yearNote.noteId, noteTitle); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         await labelService.createLabel(monthNoteId, MONTH_LABEL, monthStr); |         await labelService.createLabel(monthNote.noteId, MONTH_LABEL, monthStr); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return monthNoteId; |     return monthNote; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function getDateNoteId(dateTimeStr, rootNoteId = null) { | async function getDateNote(dateTimeStr, rootNote = null) { | ||||||
|     if (!rootNoteId) { |     if (!rootNote) { | ||||||
|         rootNoteId = await getRootCalendarNoteId(); |         rootNote = await getRootCalendarNote(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const dateStr = dateTimeStr.substr(0, 10); |     const dateStr = dateTimeStr.substr(0, 10); | ||||||
|     const dayNumber = dateTimeStr.substr(8, 2); |     const dayNumber = dateTimeStr.substr(8, 2); | ||||||
|  |  | ||||||
|     let dateNoteId = await labelService.getNoteIdWithLabel(DATE_LABEL, dateStr); |     let dateNote = await labelService.getNoteWithLabel(DATE_LABEL, dateStr); | ||||||
|  |  | ||||||
|     if (!dateNoteId) { |     if (!dateNote) { | ||||||
|         const monthNoteId = await getMonthNoteId(dateTimeStr, rootNoteId); |         const monthNote = await getMonthNote(dateTimeStr, rootNote); | ||||||
|  |  | ||||||
|         dateNoteId = await getNoteStartingWith(monthNoteId, dayNumber); |         dateNote = await getNoteStartingWith(monthNote.noteId, dayNumber); | ||||||
|  |  | ||||||
|         if (!dateNoteId) { |         if (!dateNote) { | ||||||
|             const dateObj = dateUtils.parseDate(dateTimeStr); |             const dateObj = dateUtils.parseDate(dateTimeStr); | ||||||
|  |  | ||||||
|             const noteTitle = dayNumber + " - " + DAYS[dateObj.getDay()]; |             const noteTitle = dayNumber + " - " + DAYS[dateObj.getDay()]; | ||||||
|  |  | ||||||
|             dateNoteId = await createNote(monthNoteId, noteTitle); |             dateNote = await createNote(monthNote.noteId, noteTitle); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         await labelService.createLabel(dateNoteId, DATE_LABEL, dateStr); |         await labelService.createLabel(dateNote.noteId, DATE_LABEL, dateStr); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return dateNoteId; |     return dateNote; | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     getRootCalendarNoteId, |     getRootCalendarNote, | ||||||
|     getYearNoteId, |     getYearNote, | ||||||
|     getMonthNoteId, |     getMonthNote, | ||||||
|     getDateNoteId |     getDateNote | ||||||
| }; | }; | ||||||
| @@ -1,6 +1,5 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| const sql = require('./sql'); |  | ||||||
| const repository = require('./repository'); | const repository = require('./repository'); | ||||||
| const Label = require('../entities/label'); | const Label = require('../entities/label'); | ||||||
|  |  | ||||||
| @@ -12,17 +11,10 @@ const BUILTIN_LABELS = [ | |||||||
|     'run', |     'run', | ||||||
|     'manualTransactionHandling', |     'manualTransactionHandling', | ||||||
|     'disableInclusion', |     'disableInclusion', | ||||||
|     'appCss' |     'appCss', | ||||||
|  |     'hideChildrenOverview' | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| async function getNoteIdWithLabel(name, value) { |  | ||||||
|     return await sql.getValue(`SELECT notes.noteId FROM notes JOIN labels USING(noteId)  |  | ||||||
|           WHERE notes.isDeleted = 0 |  | ||||||
|                 AND labels.isDeleted = 0 |  | ||||||
|                 AND labels.name = ?  |  | ||||||
|                 AND labels.value = ?`, [name, value]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getNotesWithLabel(name, value) { | async function getNotesWithLabel(name, value) { | ||||||
|     let notes; |     let notes; | ||||||
|  |  | ||||||
| @@ -44,11 +36,6 @@ async function getNoteWithLabel(name, value) { | |||||||
|     return notes.length > 0 ? notes[0] : null; |     return notes.length > 0 ? notes[0] : null; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function getNoteIdsWithLabel(name) { |  | ||||||
|     return await sql.getColumn(`SELECT DISTINCT notes.noteId FROM notes JOIN labels USING(noteId)  |  | ||||||
|           WHERE notes.isDeleted = 0 AND labels.isDeleted = 0 AND labels.name = ? AND labels.isDeleted = 0`, [name]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function createLabel(noteId, name, value = "") { | async function createLabel(noteId, name, value = "") { | ||||||
|     return await new Label({ |     return await new Label({ | ||||||
|         noteId: noteId, |         noteId: noteId, | ||||||
| @@ -58,10 +45,8 @@ async function createLabel(noteId, name, value = "") { | |||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     getNoteIdWithLabel, |  | ||||||
|     getNotesWithLabel, |     getNotesWithLabel, | ||||||
|     getNoteWithLabel, |     getNoteWithLabel, | ||||||
|     getNoteIdsWithLabel, |  | ||||||
|     createLabel, |     createLabel, | ||||||
|     BUILTIN_LABELS |     BUILTIN_LABELS | ||||||
| }; | }; | ||||||
| @@ -15,14 +15,22 @@ const logger = require('simple-node-logger').createRollingFileLogger({ | |||||||
| }); | }); | ||||||
|  |  | ||||||
| function info(message) { | function info(message) { | ||||||
|     logger.info(message); |     // info messages are logged asynchronously | ||||||
|  |     setTimeout(() => { | ||||||
|  |         console.log(message); | ||||||
|  |  | ||||||
|     console.log(message); |         logger.info(message); | ||||||
|  |     }, 0); | ||||||
| } | } | ||||||
|  |  | ||||||
| function error(message) { | function error(message) { | ||||||
|  |     message = "ERROR: " + message; | ||||||
|  |  | ||||||
|     // we're using .info() instead of .error() because simple-node-logger emits weird error for showError() |     // we're using .info() instead of .error() because simple-node-logger emits weird error for showError() | ||||||
|     info("ERROR: " + message); |     // errors are logged synchronously to make sure it doesn't get lost in case of crash | ||||||
|  |     logger.info(message); | ||||||
|  |  | ||||||
|  |     console.trace(message); | ||||||
| } | } | ||||||
|  |  | ||||||
| const requestBlacklist = [ "/libraries", "/javascripts", "/images", "/stylesheets" ]; | const requestBlacklist = [ "/libraries", "/javascripts", "/images", "/stylesheets" ]; | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ async function migrate() { | |||||||
|             // needs to happen outside of the transaction (otherwise it's a NO-OP) |             // needs to happen outside of the transaction (otherwise it's a NO-OP) | ||||||
|             await sql.execute("PRAGMA foreign_keys = OFF"); |             await sql.execute("PRAGMA foreign_keys = OFF"); | ||||||
|  |  | ||||||
|             await sql.doInTransaction(async () => { |             await sql.transactional(async () => { | ||||||
|                 if (mig.type === 'sql') { |                 if (mig.type === 'sql') { | ||||||
|                     const migrationSql = fs.readFileSync(resourceDir.MIGRATIONS_DIR + "/" + mig.file).toString('utf8'); |                     const migrationSql = fs.readFileSync(resourceDir.MIGRATIONS_DIR + "/" + mig.file).toString('utf8'); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -56,6 +56,7 @@ async function createNewNote(parentNoteId, noteData) { | |||||||
|         noteId: note.noteId, |         noteId: note.noteId, | ||||||
|         parentNoteId: parentNoteId, |         parentNoteId: parentNoteId, | ||||||
|         notePosition: newNotePos, |         notePosition: newNotePos, | ||||||
|  |         prefix: noteData.prefix, | ||||||
|         isExpanded: 0 |         isExpanded: 0 | ||||||
|     }).save(); |     }).save(); | ||||||
|  |  | ||||||
| @@ -180,6 +181,8 @@ async function saveNoteRevision(note) { | |||||||
|             // title and text should be decrypted now |             // title and text should be decrypted now | ||||||
|             title: note.title, |             title: note.title, | ||||||
|             content: note.content, |             content: note.content, | ||||||
|  |             type: note.type, | ||||||
|  |             mime: note.mime, | ||||||
|             isProtected: 0, // will be fixed in the protectNoteRevisions() call |             isProtected: 0, // will be fixed in the protectNoteRevisions() call | ||||||
|             dateModifiedFrom: note.dateModified, |             dateModifiedFrom: note.dateModified, | ||||||
|             dateModifiedTo: dateUtils.nowDate() |             dateModifiedTo: dateUtils.nowDate() | ||||||
| @@ -198,7 +201,7 @@ async function updateNote(noteId, noteUpdates) { | |||||||
|     await saveNoteRevision(note); |     await saveNoteRevision(note); | ||||||
|  |  | ||||||
|     note.title = noteUpdates.title; |     note.title = noteUpdates.title; | ||||||
|     note.content = noteUpdates.content; |     note.setContent(noteUpdates.content); | ||||||
|     note.isProtected = noteUpdates.isProtected; |     note.isProtected = noteUpdates.isProtected; | ||||||
|     await note.save(); |     await note.save(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -50,7 +50,7 @@ async function updateEntity(entity) { | |||||||
|  |  | ||||||
|     delete clone.jsonContent; |     delete clone.jsonContent; | ||||||
|  |  | ||||||
|     await sql.doInTransaction(async () => { |     await sql.transactional(async () => { | ||||||
|         await sql.replace(entity.constructor.tableName, clone); |         await sql.replace(entity.constructor.tableName, clone); | ||||||
|  |  | ||||||
|         const primaryKey = entity[entity.constructor.primaryKeyName]; |         const primaryKey = entity[entity.constructor.primaryKeyName]; | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| const scriptService = require('./script'); | const scriptService = require('./script'); | ||||||
| const repository = require('./repository'); | const repository = require('./repository'); | ||||||
| const cls = require('./cls'); | const cls = require('./cls'); | ||||||
|  | const sqlInit = require('./sql_init'); | ||||||
|  |  | ||||||
| async function runNotesWithLabel(runAttrValue) { | async function runNotesWithLabel(runAttrValue) { | ||||||
|     const notes = await repository.getEntities(` |     const notes = await repository.getEntities(` | ||||||
| @@ -19,8 +20,10 @@ async function runNotesWithLabel(runAttrValue) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000); | sqlInit.dbReady.then(() => { | ||||||
|  |     setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000); | ||||||
|  |  | ||||||
| setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000); |     setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000); | ||||||
|  |  | ||||||
| setInterval(cls.wrap(() => runNotesWithLabel('daily')), 24 * 3600 * 1000); |     setInterval(cls.wrap(() => runNotesWithLabel('daily')), 24 * 3600 * 1000); | ||||||
|  | }); | ||||||
| @@ -27,7 +27,7 @@ async function executeBundle(bundle, startNote) { | |||||||
|         return await execute(ctx, script, ''); |         return await execute(ctx, script, ''); | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|         return await sql.doInTransaction(async () => execute(ctx, script, '')); |         return await sql.transactional(async () => execute(ctx, script, '')); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -56,10 +56,10 @@ function ScriptApi(startNote, currentNote) { | |||||||
|  |  | ||||||
|     this.log = message => log.info(`Script ${currentNote.noteId}: ${message}`); |     this.log = message => log.info(`Script ${currentNote.noteId}: ${message}`); | ||||||
|  |  | ||||||
|     this.getRootCalendarNoteId = dateNoteService.getRootCalendarNoteId; |     this.getRootCalendarNote = dateNoteService.getRootCalendarNote; | ||||||
|     this.getDateNoteId = dateNoteService.getDateNoteId; |     this.getDateNote = dateNoteService.getDateNote; | ||||||
|  |  | ||||||
|     this.transactional = sql.doInTransaction; |     this.transactional = sql.transactional; | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = ScriptContext; | module.exports = ScriptContext; | ||||||
| @@ -122,7 +122,7 @@ async function wrap(func) { | |||||||
| let transactionActive = false; | let transactionActive = false; | ||||||
| let transactionPromise = null; | let transactionPromise = null; | ||||||
|  |  | ||||||
| async function doInTransaction(func) { | async function transactional(func) { | ||||||
|     if (cls.namespace.get('isInTransaction')) { |     if (cls.namespace.get('isInTransaction')) { | ||||||
|         return await func(); |         return await func(); | ||||||
|     } |     } | ||||||
| @@ -149,11 +149,13 @@ async function doInTransaction(func) { | |||||||
|             resolve(); |             resolve(); | ||||||
|         } |         } | ||||||
|         catch (e) { |         catch (e) { | ||||||
|             log.error("Error executing transaction, executing rollback. Inner exception: " + e.stack + error.stack); |             if (transactionActive) { | ||||||
|  |                 log.error("Error executing transaction, executing rollback. Inner exception: " + e.stack + error.stack); | ||||||
|  |  | ||||||
|             await rollback(); |                 await rollback(); | ||||||
|  |  | ||||||
|             transactionActive = false; |                 transactionActive = false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             reject(e); |             reject(e); | ||||||
|         } |         } | ||||||
| @@ -181,5 +183,5 @@ module.exports = { | |||||||
|     getColumn, |     getColumn, | ||||||
|     execute, |     execute, | ||||||
|     executeScript, |     executeScript, | ||||||
|     doInTransaction |     transactional | ||||||
| }; | }; | ||||||
| @@ -58,7 +58,7 @@ async function createInitialDatabase() { | |||||||
|     const imagesSql = fs.readFileSync(resourceDir.DB_INIT_DIR + '/main_images.sql', 'UTF-8'); |     const imagesSql = fs.readFileSync(resourceDir.DB_INIT_DIR + '/main_images.sql', 'UTF-8'); | ||||||
|     const notesImageSql = fs.readFileSync(resourceDir.DB_INIT_DIR + '/main_note_images.sql', 'UTF-8'); |     const notesImageSql = fs.readFileSync(resourceDir.DB_INIT_DIR + '/main_note_images.sql', 'UTF-8'); | ||||||
|  |  | ||||||
|     await sql.doInTransaction(async () => { |     await sql.transactional(async () => { | ||||||
|         await sql.executeScript(schema); |         await sql.executeScript(schema); | ||||||
|         await sql.executeScript(notesSql); |         await sql.executeScript(notesSql); | ||||||
|         await sql.executeScript(notesTreeSql); |         await sql.executeScript(notesTreeSql); | ||||||
|   | |||||||
| @@ -10,10 +10,8 @@ const sourceIdService = require('./source_id'); | |||||||
| const dateUtils = require('./date_utils'); | const dateUtils = require('./date_utils'); | ||||||
| const syncUpdateService = require('./sync_update'); | const syncUpdateService = require('./sync_update'); | ||||||
| const contentHashService = require('./content_hash'); | const contentHashService = require('./content_hash'); | ||||||
| const eventLogService = require('./event_log'); |  | ||||||
| const fs = require('fs'); | const fs = require('fs'); | ||||||
| const appInfo = require('./app_info'); | const appInfo = require('./app_info'); | ||||||
| const messagingService = require('./messaging'); |  | ||||||
| const syncSetup = require('./sync_setup'); | const syncSetup = require('./sync_setup'); | ||||||
| const syncMutexService = require('./sync_mutex'); | const syncMutexService = require('./sync_mutex'); | ||||||
| const cls = require('./cls'); | const cls = require('./cls'); | ||||||
| @@ -91,69 +89,19 @@ async function login() { | |||||||
|     return syncContext; |     return syncContext; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function getLastSyncedPull() { |  | ||||||
|     return parseInt(await optionService.getOption('lastSyncedPull')); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function setLastSyncedPull(syncId) { |  | ||||||
|     await optionService.setOption('lastSyncedPull', syncId); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function pullSync(syncContext) { | async function pullSync(syncContext) { | ||||||
|     const lastSyncedPull = await getLastSyncedPull(); |     const changesUri = '/api/sync/changed?lastSyncId=' + await getLastSyncedPull(); | ||||||
|  |  | ||||||
|     const changesUri = '/api/sync/changed?lastSyncId=' + lastSyncedPull; |     const rows = await syncRequest(syncContext, 'GET', changesUri); | ||||||
|  |  | ||||||
|     const syncRows = await syncRequest(syncContext, 'GET', changesUri); |     log.info("Pulled " + rows.length + " changes from " + changesUri); | ||||||
|  |  | ||||||
|     log.info("Pulled " + syncRows.length + " changes from " + changesUri); |     for (const {sync, entity} of rows) { | ||||||
|  |  | ||||||
|     for (const sync of syncRows) { |  | ||||||
|         if (sourceIdService.isLocalSourceId(sync.sourceId)) { |         if (sourceIdService.isLocalSourceId(sync.sourceId)) { | ||||||
|             log.info(`Skipping pull #${sync.id} ${sync.entityName} ${sync.entityId} because ${sync.sourceId} is a local source id.`); |             log.info(`Skipping pull #${sync.id} ${sync.entityName} ${sync.entityId} because ${sync.sourceId} is a local source id.`); | ||||||
|  |  | ||||||
|             await setLastSyncedPull(sync.id); |  | ||||||
|  |  | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         const resp = await syncRequest(syncContext, 'GET', "/api/sync/" + sync.entityName + "/" + encodeURIComponent(sync.entityId)); |  | ||||||
|  |  | ||||||
|         if (!resp || (sync.entityName === 'notes' && !resp.entity)) { |  | ||||||
|             log.error(`Empty response to pull for sync #${sync.id} ${sync.entityName}, id=${sync.entityId}`); |  | ||||||
|         } |  | ||||||
|         else if (sync.entityName === 'notes') { |  | ||||||
|             await syncUpdateService.updateNote(resp.entity, syncContext.sourceId); |  | ||||||
|         } |  | ||||||
|         else if (sync.entityName === 'branches') { |  | ||||||
|             await syncUpdateService.updateBranch(resp, syncContext.sourceId); |  | ||||||
|         } |  | ||||||
|         else if (sync.entityName === 'note_revisions') { |  | ||||||
|             await syncUpdateService.updateNoteRevision(resp, syncContext.sourceId); |  | ||||||
|         } |  | ||||||
|         else if (sync.entityName === 'note_reordering') { |  | ||||||
|             await syncUpdateService.updateNoteReordering(resp, syncContext.sourceId); |  | ||||||
|         } |  | ||||||
|         else if (sync.entityName === 'options') { |  | ||||||
|             await syncUpdateService.updateOptions(resp, syncContext.sourceId); |  | ||||||
|         } |  | ||||||
|         else if (sync.entityName === 'recent_notes') { |  | ||||||
|             await syncUpdateService.updateRecentNotes(resp, syncContext.sourceId); |  | ||||||
|         } |  | ||||||
|         else if (sync.entityName === 'images') { |  | ||||||
|             await syncUpdateService.updateImage(resp, syncContext.sourceId); |  | ||||||
|         } |  | ||||||
|         else if (sync.entityName === 'note_images') { |  | ||||||
|             await syncUpdateService.updateNoteImage(resp, syncContext.sourceId); |  | ||||||
|         } |  | ||||||
|         else if (sync.entityName === 'labels') { |  | ||||||
|             await syncUpdateService.updateLabel(resp, syncContext.sourceId); |  | ||||||
|         } |  | ||||||
|         else if (sync.entityName === 'api_tokens') { |  | ||||||
|             await syncUpdateService.updateApiToken(resp, syncContext.sourceId); |  | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             throw new Error(`Unrecognized entity type ${sync.entityName} in sync #${sync.id}`); |             await syncUpdateService.updateEntity(sync.entityName, entity, syncContext.sourceId); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         await setLastSyncedPull(sync.id); |         await setLastSyncedPull(sync.id); | ||||||
| @@ -162,145 +110,69 @@ async function pullSync(syncContext) { | |||||||
|     log.info("Finished pull"); |     log.info("Finished pull"); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function getLastSyncedPush() { |  | ||||||
|     return parseInt(await optionService.getOption('lastSyncedPush')); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function setLastSyncedPush(lastSyncedPush) { |  | ||||||
|     await optionService.setOption('lastSyncedPush', lastSyncedPush); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function pushSync(syncContext) { | async function pushSync(syncContext) { | ||||||
|     let lastSyncedPush = await getLastSyncedPush(); |     let lastSyncedPush = await getLastSyncedPush(); | ||||||
|  |  | ||||||
|     while (true) { |     while (true) { | ||||||
|         const sync = await sql.getRowOrNull('SELECT * FROM sync WHERE id > ? LIMIT 1', [lastSyncedPush]); |         const syncs = await sql.getRows('SELECT * FROM sync WHERE id > ? LIMIT 1000', [lastSyncedPush]); | ||||||
|  |  | ||||||
|         if (sync === null) { |         const filteredSyncs = syncs.filter(sync => { | ||||||
|             // nothing to sync |             if (sync.sourceId === syncContext.sourceId) { | ||||||
|  |                 log.info(`Skipping push #${sync.id} ${sync.entityName} ${sync.entityId} because it originates from sync target`); | ||||||
|  |  | ||||||
|  |                 // this may set lastSyncedPush beyond what's actually sent (because of size limit) | ||||||
|  |                 // so this is applied to the database only if there's no actual update | ||||||
|  |                 // TODO: it would be better to simplify this somehow | ||||||
|  |                 lastSyncedPush = sync.id; | ||||||
|  |  | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         if (filteredSyncs.length === 0) { | ||||||
|             log.info("Nothing to push"); |             log.info("Nothing to push"); | ||||||
|  |  | ||||||
|  |             await setLastSyncedPush(lastSyncedPush); | ||||||
|  |  | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (sync.sourceId === syncContext.sourceId) { |         const syncRecords = await getSyncRecords(filteredSyncs); | ||||||
|             log.info(`Skipping push #${sync.id} ${sync.entityName} ${sync.entityId} because it originates from sync target`); |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             await pushEntity(sync, syncContext); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         lastSyncedPush = sync.id; |         log.info(`Pushing ${syncRecords.length} syncs.`); | ||||||
|  |  | ||||||
|  |         await syncRequest(syncContext, 'PUT', '/api/sync/update', { | ||||||
|  |             sourceId: sourceIdService.getCurrentSourceId(), | ||||||
|  |             entities: syncRecords | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         lastSyncedPush = syncRecords[syncRecords.length - 1].sync.id; | ||||||
|  |  | ||||||
|         await setLastSyncedPush(lastSyncedPush); |         await setLastSyncedPush(lastSyncedPush); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function pushEntity(sync, syncContext) { |  | ||||||
|     let entity; |  | ||||||
|  |  | ||||||
|     if (sync.entityName === 'notes') { |  | ||||||
|         entity = await sql.getRow('SELECT * FROM notes WHERE noteId = ?', [sync.entityId]); |  | ||||||
|  |  | ||||||
|         serializeNoteContentBuffer(entity); |  | ||||||
|     } |  | ||||||
|     else if (sync.entityName === 'branches') { |  | ||||||
|         entity = await sql.getRow('SELECT * FROM branches WHERE branchId = ?', [sync.entityId]); |  | ||||||
|     } |  | ||||||
|     else if (sync.entityName === 'note_revisions') { |  | ||||||
|         entity = await sql.getRow('SELECT * FROM note_revisions WHERE noteRevisionId = ?', [sync.entityId]); |  | ||||||
|     } |  | ||||||
|     else if (sync.entityName === 'note_reordering') { |  | ||||||
|         entity = { |  | ||||||
|             parentNoteId: sync.entityId, |  | ||||||
|             ordering: await sql.getMap('SELECT branchId, notePosition FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [sync.entityId]) |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
|     else if (sync.entityName === 'options') { |  | ||||||
|         entity = await sql.getRow('SELECT * FROM options WHERE name = ?', [sync.entityId]); |  | ||||||
|     } |  | ||||||
|     else if (sync.entityName === 'recent_notes') { |  | ||||||
|         entity = await sql.getRow('SELECT * FROM recent_notes WHERE branchId = ?', [sync.entityId]); |  | ||||||
|     } |  | ||||||
|     else if (sync.entityName === 'images') { |  | ||||||
|         entity = await sql.getRow('SELECT * FROM images WHERE imageId = ?', [sync.entityId]); |  | ||||||
|  |  | ||||||
|         if (entity.data !== null) { |  | ||||||
|             entity.data = entity.data.toString('base64'); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     else if (sync.entityName === 'note_images') { |  | ||||||
|         entity = await sql.getRow('SELECT * FROM note_images WHERE noteImageId = ?', [sync.entityId]); |  | ||||||
|     } |  | ||||||
|     else if (sync.entityName === 'labels') { |  | ||||||
|         entity = await sql.getRow('SELECT * FROM labels WHERE labelId = ?', [sync.entityId]); |  | ||||||
|     } |  | ||||||
|     else if (sync.entityName === 'api_tokens') { |  | ||||||
|         entity = await sql.getRow('SELECT * FROM api_tokens WHERE apiTokenId = ?', [sync.entityId]); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         throw new Error(`Unrecognized entity type ${sync.entityName} in sync #${sync.id}`); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (!entity) { |  | ||||||
|         log.info(`Sync #${sync.id} entity for ${sync.entityName} ${sync.entityId} doesn't exist. Skipping.`); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     log.info(`Pushing changes in sync #${sync.id} ${sync.entityName} ${sync.entityId}`); |  | ||||||
|  |  | ||||||
|     const payload = { |  | ||||||
|         sourceId: sourceIdService.getCurrentSourceId(), |  | ||||||
|         entity: entity |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     await syncRequest(syncContext, 'PUT', '/api/sync/' + sync.entityName, payload); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function serializeNoteContentBuffer(note) { |  | ||||||
|     if (note.type === 'file') { |  | ||||||
|         note.content = note.content.toString("binary"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function checkContentHash(syncContext) { | async function checkContentHash(syncContext) { | ||||||
|     const resp = await syncRequest(syncContext, 'GET', '/api/sync/check'); |     const resp = await syncRequest(syncContext, 'GET', '/api/sync/check'); | ||||||
|  |  | ||||||
|     if (await getLastSyncedPull() < resp.max_sync_id) { |     if (await getLastSyncedPull() < resp.maxSyncId) { | ||||||
|         log.info("There are some outstanding pulls, skipping content check."); |         log.info("There are some outstanding pulls, skipping content check."); | ||||||
|  |  | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const lastSyncedPush = await getLastSyncedPush(); |     const notPushedSyncs = await sql.getValue("SELECT COUNT(*) FROM sync WHERE id > ?", [await getLastSyncedPush()]); | ||||||
|     const notPushedSyncs = await sql.getValue("SELECT COUNT(*) FROM sync WHERE id > ?", [lastSyncedPush]); |  | ||||||
|  |  | ||||||
|     if (notPushedSyncs > 0) { |     if (notPushedSyncs > 0) { | ||||||
|         log.info("There's " + notPushedSyncs + " outstanding pushes, skipping content check."); |         log.info(`There's ${notPushedSyncs} outstanding pushes, skipping content check.`); | ||||||
|  |  | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const hashes = await contentHashService.getHashes(); |     await contentHashService.checkContentHashes(resp.hashes); | ||||||
|     let allChecksPassed = true; |  | ||||||
|  |  | ||||||
|     for (const key in hashes) { |  | ||||||
|         if (hashes[key] !== resp.hashes[key]) { |  | ||||||
|             allChecksPassed = false; |  | ||||||
|  |  | ||||||
|             await eventLogService.addEvent(`Content hash check for ${key} FAILED. Local is ${hashes[key]}, remote is ${resp.hashes[key]}`); |  | ||||||
|  |  | ||||||
|             if (key !== 'recent_notes') { |  | ||||||
|                 // let's not get alarmed about recent notes which get updated often and can cause failures in race conditions |  | ||||||
|                 await messagingService.sendMessageToAllClients({type: 'sync-hash-check-failed'}); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (allChecksPassed) { |  | ||||||
|         log.info("Content hash checks PASSED"); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| async function syncRequest(syncContext, method, uri, body) { | async function syncRequest(syncContext, method, uri, body) { | ||||||
| @@ -331,6 +203,80 @@ async function syncRequest(syncContext, method, uri, body) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | const primaryKeys = { | ||||||
|  |     "notes": "noteId", | ||||||
|  |     "branches": "branchId", | ||||||
|  |     "note_revisions": "noteRevisionId", | ||||||
|  |     "option": "name", | ||||||
|  |     "recent_notes": "branchId", | ||||||
|  |     "images": "imageId", | ||||||
|  |     "note_images": "noteImageId", | ||||||
|  |     "labels": "labelId", | ||||||
|  |     "api_tokens": "apiTokenId" | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | async function getEntityRow(entityName, entityId) { | ||||||
|  |     if (entityName === 'note_reordering') { | ||||||
|  |         return await sql.getMap("SELECT branchId, notePosition FROM branches WHERE parentNoteId = ? AND isDeleted = 0", [entityId]); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         const primaryKey = primaryKeys[entityName]; | ||||||
|  |  | ||||||
|  |         if (!primaryKey) { | ||||||
|  |             throw new Error("Unknown entity " + entityName); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const entity = await sql.getRow(`SELECT * FROM ${entityName} WHERE ${primaryKey} = ?`, [entityId]); | ||||||
|  |  | ||||||
|  |         if (entityName === 'notes' && entity.type === 'file') { | ||||||
|  |             entity.content = entity.content.toString("binary"); | ||||||
|  |         } | ||||||
|  |         else if (entityName === 'images') { | ||||||
|  |             entity.data = entity.data.toString('base64'); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return entity; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function getSyncRecords(syncs) { | ||||||
|  |     const records = []; | ||||||
|  |     let length = 0; | ||||||
|  |  | ||||||
|  |     for (const sync of syncs) { | ||||||
|  |         const record = { | ||||||
|  |             sync: sync, | ||||||
|  |             entity: await getEntityRow(sync.entityName, sync.entityId) | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         records.push(record); | ||||||
|  |  | ||||||
|  |         length += JSON.stringify(record).length; | ||||||
|  |  | ||||||
|  |         if (length > 1000000) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return records; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function getLastSyncedPull() { | ||||||
|  |     return parseInt(await optionService.getOption('lastSyncedPull')); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function setLastSyncedPull(syncId) { | ||||||
|  |     await optionService.setOption('lastSyncedPull', syncId); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function getLastSyncedPush() { | ||||||
|  |     return parseInt(await optionService.getOption('lastSyncedPush')); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function setLastSyncedPush(lastSyncedPush) { | ||||||
|  |     await optionService.setOption('lastSyncedPush', lastSyncedPush); | ||||||
|  | } | ||||||
|  |  | ||||||
| sqlInit.dbReady.then(() => { | sqlInit.dbReady.then(() => { | ||||||
|     if (syncSetup.isSyncSetup) { |     if (syncSetup.isSyncSetup) { | ||||||
|         log.info("Setting up sync to " + syncSetup.SYNC_SERVER + " with timeout " + syncSetup.SYNC_TIMEOUT); |         log.info("Setting up sync to " + syncSetup.SYNC_SERVER + " with timeout " + syncSetup.SYNC_TIMEOUT); | ||||||
| @@ -357,5 +303,5 @@ sqlInit.dbReady.then(() => { | |||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     sync, |     sync, | ||||||
|     serializeNoteContentBuffer |     getSyncRecords | ||||||
| }; | }; | ||||||
| @@ -91,6 +91,8 @@ async function fillSyncRows(entityName, entityKey) { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function fillAllSyncRows() { | async function fillAllSyncRows() { | ||||||
|  |     await sql.execute("DELETE FROM sync"); | ||||||
|  |  | ||||||
|     await fillSyncRows("notes", "noteId"); |     await fillSyncRows("notes", "noteId"); | ||||||
|     await fillSyncRows("branches", "branchId"); |     await fillSyncRows("branches", "branchId"); | ||||||
|     await fillSyncRows("note_revisions", "noteRevisionId"); |     await fillSyncRows("note_revisions", "noteRevisionId"); | ||||||
|   | |||||||
| @@ -3,6 +3,42 @@ const log = require('./log'); | |||||||
| const eventLogService = require('./event_log'); | const eventLogService = require('./event_log'); | ||||||
| const syncTableService = require('./sync_table'); | const syncTableService = require('./sync_table'); | ||||||
|  |  | ||||||
|  | async function updateEntity(entityName, entity, sourceId) { | ||||||
|  |     if (entityName === 'notes') { | ||||||
|  |         await updateNote(entity, sourceId); | ||||||
|  |     } | ||||||
|  |     else if (entityName === 'branches') { | ||||||
|  |         await updateBranch(entity, sourceId); | ||||||
|  |     } | ||||||
|  |     else if (entityName === 'note_revisions') { | ||||||
|  |         await updateNoteRevision(entity, sourceId); | ||||||
|  |     } | ||||||
|  |     else if (entityName === 'note_reordering') { | ||||||
|  |         await updateNoteReordering(entity, sourceId); | ||||||
|  |     } | ||||||
|  |     else if (entityName === 'options') { | ||||||
|  |         await updateOptions(entity, sourceId); | ||||||
|  |     } | ||||||
|  |     else if (entityName === 'recent_notes') { | ||||||
|  |         await updateRecentNotes(entity, sourceId); | ||||||
|  |     } | ||||||
|  |     else if (entityName === 'images') { | ||||||
|  |         await updateImage(entity, sourceId); | ||||||
|  |     } | ||||||
|  |     else if (entityName === 'note_images') { | ||||||
|  |         await updateNoteImage(entity, sourceId); | ||||||
|  |     } | ||||||
|  |     else if (entityName === 'labels') { | ||||||
|  |         await updateLabel(entity, sourceId); | ||||||
|  |     } | ||||||
|  |     else if (entityName === 'api_tokens') { | ||||||
|  |         await updateApiToken(entity, sourceId); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         throw new Error(`Unrecognized entity type ${entityName}`); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| function deserializeNoteContentBuffer(note) { | function deserializeNoteContentBuffer(note) { | ||||||
|     if (note.type === 'file') { |     if (note.type === 'file') { | ||||||
|         note.content = new Buffer(note.content, 'binary'); |         note.content = new Buffer(note.content, 'binary'); | ||||||
| @@ -15,7 +51,7 @@ async function updateNote(entity, sourceId) { | |||||||
|     const origNote = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [entity.noteId]); |     const origNote = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [entity.noteId]); | ||||||
|  |  | ||||||
|     if (!origNote || origNote.dateModified <= entity.dateModified) { |     if (!origNote || origNote.dateModified <= entity.dateModified) { | ||||||
|         await sql.doInTransaction(async () => { |         await sql.transactional(async () => { | ||||||
|             await sql.replace("notes", entity); |             await sql.replace("notes", entity); | ||||||
|  |  | ||||||
|             await syncTableService.addNoteSync(entity.noteId, sourceId); |             await syncTableService.addNoteSync(entity.noteId, sourceId); | ||||||
| @@ -29,7 +65,7 @@ async function updateNote(entity, sourceId) { | |||||||
| async function updateBranch(entity, sourceId) { | async function updateBranch(entity, sourceId) { | ||||||
|     const orig = await sql.getRowOrNull("SELECT * FROM branches WHERE branchId = ?", [entity.branchId]); |     const orig = await sql.getRowOrNull("SELECT * FROM branches WHERE branchId = ?", [entity.branchId]); | ||||||
|  |  | ||||||
|     await sql.doInTransaction(async () => { |     await sql.transactional(async () => { | ||||||
|         if (orig === null || orig.dateModified < entity.dateModified) { |         if (orig === null || orig.dateModified < entity.dateModified) { | ||||||
|             delete entity.isExpanded; |             delete entity.isExpanded; | ||||||
|  |  | ||||||
| @@ -45,7 +81,7 @@ async function updateBranch(entity, sourceId) { | |||||||
| async function updateNoteRevision(entity, sourceId) { | async function updateNoteRevision(entity, sourceId) { | ||||||
|     const orig = await sql.getRowOrNull("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [entity.noteRevisionId]); |     const orig = await sql.getRowOrNull("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [entity.noteRevisionId]); | ||||||
|  |  | ||||||
|     await sql.doInTransaction(async () => { |     await sql.transactional(async () => { | ||||||
|         // we update note revision even if date modified to is the same because the only thing which might have changed |         // we update note revision even if date modified to is the same because the only thing which might have changed | ||||||
|         // is the protected status (and correnspondingly title and content) which doesn't affect the dateModifiedTo |         // is the protected status (and correnspondingly title and content) which doesn't affect the dateModifiedTo | ||||||
|         if (orig === null || orig.dateModifiedTo <= entity.dateModifiedTo) { |         if (orig === null || orig.dateModifiedTo <= entity.dateModifiedTo) { | ||||||
| @@ -59,7 +95,7 @@ async function updateNoteRevision(entity, sourceId) { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function updateNoteReordering(entity, sourceId) { | async function updateNoteReordering(entity, sourceId) { | ||||||
|     await sql.doInTransaction(async () => { |     await sql.transactional(async () => { | ||||||
|         Object.keys(entity.ordering).forEach(async key => { |         Object.keys(entity.ordering).forEach(async key => { | ||||||
|             await sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity.ordering[key], key]); |             await sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity.ordering[key], key]); | ||||||
|         }); |         }); | ||||||
| @@ -75,7 +111,7 @@ async function updateOptions(entity, sourceId) { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     await sql.doInTransaction(async () => { |     await sql.transactional(async () => { | ||||||
|         if (orig === null || orig.dateModified < entity.dateModified) { |         if (orig === null || orig.dateModified < entity.dateModified) { | ||||||
|             await sql.replace('options', entity); |             await sql.replace('options', entity); | ||||||
|  |  | ||||||
| @@ -90,7 +126,7 @@ async function updateRecentNotes(entity, sourceId) { | |||||||
|     const orig = await sql.getRowOrNull("SELECT * FROM recent_notes WHERE branchId = ?", [entity.branchId]); |     const orig = await sql.getRowOrNull("SELECT * FROM recent_notes WHERE branchId = ?", [entity.branchId]); | ||||||
|  |  | ||||||
|     if (orig === null || orig.dateAccessed < entity.dateAccessed) { |     if (orig === null || orig.dateAccessed < entity.dateAccessed) { | ||||||
|         await sql.doInTransaction(async () => { |         await sql.transactional(async () => { | ||||||
|             await sql.replace('recent_notes', entity); |             await sql.replace('recent_notes', entity); | ||||||
|  |  | ||||||
|             await syncTableService.addRecentNoteSync(entity.branchId, sourceId); |             await syncTableService.addRecentNoteSync(entity.branchId, sourceId); | ||||||
| @@ -106,7 +142,7 @@ async function updateImage(entity, sourceId) { | |||||||
|     const origImage = await sql.getRow("SELECT * FROM images WHERE imageId = ?", [entity.imageId]); |     const origImage = await sql.getRow("SELECT * FROM images WHERE imageId = ?", [entity.imageId]); | ||||||
|  |  | ||||||
|     if (!origImage || origImage.dateModified <= entity.dateModified) { |     if (!origImage || origImage.dateModified <= entity.dateModified) { | ||||||
|         await sql.doInTransaction(async () => { |         await sql.transactional(async () => { | ||||||
|             await sql.replace("images", entity); |             await sql.replace("images", entity); | ||||||
|  |  | ||||||
|             await syncTableService.addImageSync(entity.imageId, sourceId); |             await syncTableService.addImageSync(entity.imageId, sourceId); | ||||||
| @@ -120,7 +156,7 @@ async function updateNoteImage(entity, sourceId) { | |||||||
|     const origNoteImage = await sql.getRow("SELECT * FROM note_images WHERE noteImageId = ?", [entity.noteImageId]); |     const origNoteImage = await sql.getRow("SELECT * FROM note_images WHERE noteImageId = ?", [entity.noteImageId]); | ||||||
|  |  | ||||||
|     if (!origNoteImage || origNoteImage.dateModified <= entity.dateModified) { |     if (!origNoteImage || origNoteImage.dateModified <= entity.dateModified) { | ||||||
|         await sql.doInTransaction(async () => { |         await sql.transactional(async () => { | ||||||
|             await sql.replace("note_images", entity); |             await sql.replace("note_images", entity); | ||||||
|  |  | ||||||
|             await syncTableService.addNoteImageSync(entity.noteImageId, sourceId); |             await syncTableService.addNoteImageSync(entity.noteImageId, sourceId); | ||||||
| @@ -134,7 +170,7 @@ async function updateLabel(entity, sourceId) { | |||||||
|     const origLabel = await sql.getRow("SELECT * FROM labels WHERE labelId = ?", [entity.labelId]); |     const origLabel = await sql.getRow("SELECT * FROM labels WHERE labelId = ?", [entity.labelId]); | ||||||
|  |  | ||||||
|     if (!origLabel || origLabel.dateModified <= entity.dateModified) { |     if (!origLabel || origLabel.dateModified <= entity.dateModified) { | ||||||
|         await sql.doInTransaction(async () => { |         await sql.transactional(async () => { | ||||||
|             await sql.replace("labels", entity); |             await sql.replace("labels", entity); | ||||||
|  |  | ||||||
|             await syncTableService.addLabelSync(entity.labelId, sourceId); |             await syncTableService.addLabelSync(entity.labelId, sourceId); | ||||||
| @@ -148,7 +184,7 @@ async function updateApiToken(entity, sourceId) { | |||||||
|     const apiTokenId = await sql.getRow("SELECT * FROM api_tokens WHERE apiTokenId = ?", [entity.apiTokenId]); |     const apiTokenId = await sql.getRow("SELECT * FROM api_tokens WHERE apiTokenId = ?", [entity.apiTokenId]); | ||||||
|  |  | ||||||
|     if (!apiTokenId) { |     if (!apiTokenId) { | ||||||
|         await sql.doInTransaction(async () => { |         await sql.transactional(async () => { | ||||||
|             await sql.replace("api_tokens", entity); |             await sql.replace("api_tokens", entity); | ||||||
|  |  | ||||||
|             await syncTableService.addApiTokenSync(entity.apiTokenId, sourceId); |             await syncTableService.addApiTokenSync(entity.apiTokenId, sourceId); | ||||||
| @@ -159,14 +195,5 @@ async function updateApiToken(entity, sourceId) { | |||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     updateNote, |     updateEntity | ||||||
|     updateBranch, |  | ||||||
|     updateNoteRevision, |  | ||||||
|     updateNoteReordering, |  | ||||||
|     updateOptions, |  | ||||||
|     updateRecentNotes, |  | ||||||
|     updateImage, |  | ||||||
|     updateNoteImage, |  | ||||||
|     updateLabel, |  | ||||||
|     updateApiToken |  | ||||||
| }; | }; | ||||||
| @@ -77,7 +77,7 @@ async function loadSubTreeNoteIds(parentNoteId, subTreeNoteIds) { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function sortNotesAlphabetically(parentNoteId) { | async function sortNotesAlphabetically(parentNoteId) { | ||||||
|     await sql.doInTransaction(async () => { |     await sql.transactional(async () => { | ||||||
|         const notes = await sql.getRows(`SELECT branchId, noteId, title, isProtected  |         const notes = await sql.getRows(`SELECT branchId, noteId, title, isProtected  | ||||||
|                                        FROM notes JOIN branches USING(noteId)  |                                        FROM notes JOIN branches USING(noteId)  | ||||||
|                                        WHERE branches.isDeleted = 0 AND parentNoteId = ?`, [parentNoteId]); |                                        WHERE branches.isDeleted = 0 AND parentNoteId = ?`, [parentNoteId]); | ||||||
|   | |||||||
| @@ -132,76 +132,80 @@ | |||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       <div style="position: relative; overflow: auto; grid-area: note-content; padding-left: 10px; padding-top: 10px;" id="note-detail-wrapper"> |       <div style="position: relative; overflow: hidden; grid-area: note-detail; padding-left: 10px; padding-top: 10px; display: flex; flex-direction: column;" id="note-detail-wrapper"> | ||||||
|         <div id="note-detail-text" class="note-detail-component"></div> |         <div style="flex-grow: 1; position: relative; overflow: auto; flex-basis: content;"> | ||||||
|  |           <div id="note-detail-text" style="height: 100%;" class="note-detail-component"></div> | ||||||
|  |  | ||||||
|         <div id="note-detail-search" class="note-detail-component"> |           <div id="note-detail-search" class="note-detail-component"> | ||||||
|           <div style="display: flex; align-items: center;"> |             <div style="display: flex; align-items: center;"> | ||||||
|             <strong>Search string:    </strong> |               <strong>Search string:    </strong> | ||||||
|             <textarea rows="4" cols="50" id="search-string"></textarea> |               <textarea rows="4" cols="50" id="search-string"></textarea> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <br /> | ||||||
|  |  | ||||||
|  |             <h4>Help</h4> | ||||||
|  |             <p> | ||||||
|  |               <ul> | ||||||
|  |                 <li> | ||||||
|  |                   <code>@abc</code> - matches notes with label abc</li> | ||||||
|  |                 <li> | ||||||
|  |                   <code>@!abc</code> - matches notes without abc label (maybe not the best syntax)</li> | ||||||
|  |                 <li> | ||||||
|  |                   <code>@abc=true</code> - matches notes with label abc having value true</li> | ||||||
|  |                 <li><code>@abc!=true</code></li> | ||||||
|  |                 <li> | ||||||
|  |                   <code>@"weird label"="weird value"</code> - works also with whitespace inside names values</li> | ||||||
|  |                 <li> | ||||||
|  |                   <code>@abc and @def</code> - matches notes with both abc and def</li> | ||||||
|  |                 <li> | ||||||
|  |                   <code>@abc @def</code> - AND relation is implicit when specifying multiple labels</li> | ||||||
|  |                 <li> | ||||||
|  |                   <code>@abc or @def</code> - OR relation</li> | ||||||
|  |                 <li> | ||||||
|  |                   <code>@abc<=5</code> - numerical comparison (also >, >=, <).</li> | ||||||
|  |                 <li> | ||||||
|  |                   <code>some search string @abc @def</code> - combination of fulltext and label search - both of them need to match (OR not supported)</li> | ||||||
|  |                 <li> | ||||||
|  |                   <code>@abc @def some search string</code> - same combination</li> | ||||||
|  |               </ul> | ||||||
|  |  | ||||||
|  |               <a href="https://github.com/zadam/trilium/wiki/Labels">Complete help on search syntax</a> | ||||||
|  |             </p> | ||||||
|           </div> |           </div> | ||||||
|  |  | ||||||
|           <br /> |           <div id="note-detail-code" class="note-detail-component"></div> | ||||||
|  |  | ||||||
|           <h4>Help</h4> |           <div id="note-detail-render" class="note-detail-component"></div> | ||||||
|           <p> |  | ||||||
|             <ul> |  | ||||||
|               <li> |  | ||||||
|                 <code>@abc</code> - matches notes with label abc</li> |  | ||||||
|               <li> |  | ||||||
|                 <code>@!abc</code> - matches notes without abc label (maybe not the best syntax)</li> |  | ||||||
|               <li> |  | ||||||
|                 <code>@abc=true</code> - matches notes with label abc having value true</li> |  | ||||||
|               <li><code>@abc!=true</code></li> |  | ||||||
|               <li> |  | ||||||
|                 <code>@"weird label"="weird value"</code> - works also with whitespace inside names values</li> |  | ||||||
|               <li> |  | ||||||
|                 <code>@abc and @def</code> - matches notes with both abc and def</li> |  | ||||||
|               <li> |  | ||||||
|                 <code>@abc @def</code> - AND relation is implicit when specifying multiple labels</li> |  | ||||||
|               <li> |  | ||||||
|                 <code>@abc or @def</code> - OR relation</li> |  | ||||||
|               <li> |  | ||||||
|                 <code>@abc<=5</code> - numerical comparison (also >, >=, <).</li> |  | ||||||
|               <li> |  | ||||||
|                 <code>some search string @abc @def</code> - combination of fulltext and label search - both of them need to match (OR not supported)</li> |  | ||||||
|               <li> |  | ||||||
|                 <code>@abc @def some search string</code> - same combination</li> |  | ||||||
|             </ul> |  | ||||||
|  |  | ||||||
|             <a href="https://github.com/zadam/trilium/wiki/Labels">Complete help on search syntax</a> |           <div id="note-detail-file" class="note-detail-component"> | ||||||
|           </p> |             <table id="file-table"> | ||||||
|  |               <tr> | ||||||
|  |                 <th>File name:</th> | ||||||
|  |                 <td id="file-filename"></td> | ||||||
|  |               </tr> | ||||||
|  |               <tr> | ||||||
|  |                 <th>File type:</th> | ||||||
|  |                 <td id="file-filetype"></td> | ||||||
|  |               </tr> | ||||||
|  |               <tr> | ||||||
|  |                 <th>File size:</th> | ||||||
|  |                 <td id="file-filesize"></td> | ||||||
|  |               </tr> | ||||||
|  |               <tr> | ||||||
|  |                 <td> | ||||||
|  |                   <button id="file-download" class="btn btn-primary" type="button">Download</button> | ||||||
|  |                     | ||||||
|  |                   <button id="file-open" class="btn btn-primary" type="button">Open</button> | ||||||
|  |                 </td> | ||||||
|  |               </tr> | ||||||
|  |             </table> | ||||||
|  |           </div> | ||||||
|  |  | ||||||
|  |           <input type="file" id="file-upload" style="display: none" /> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|         <div id="note-detail-code" class="note-detail-component"></div> |         <div id="children-overview"></div> | ||||||
|  |  | ||||||
|         <div id="note-detail-render" class="note-detail-component"></div> |  | ||||||
|  |  | ||||||
|         <div id="note-detail-file" class="note-detail-component"> |  | ||||||
|           <table id="file-table"> |  | ||||||
|             <tr> |  | ||||||
|               <th>File name:</th> |  | ||||||
|               <td id="file-filename"></td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <th>File type:</th> |  | ||||||
|               <td id="file-filetype"></td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <th>File size:</th> |  | ||||||
|               <td id="file-filesize"></td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <td> |  | ||||||
|                 <button id="file-download" class="btn btn-primary" type="button">Download</button> |  | ||||||
|                   |  | ||||||
|                 <button id="file-open" class="btn btn-primary" type="button">Open</button> |  | ||||||
|               </td> |  | ||||||
|             </tr> |  | ||||||
|           </table> |  | ||||||
|         </div> |  | ||||||
|  |  | ||||||
|         <input type="file" id="file-upload" style="display: none" /> |  | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       <div id="label-list"> |       <div id="label-list"> | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								src/www
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								src/www
									
									
									
									
									
								
							| @@ -18,8 +18,6 @@ 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 sql = require('./services/sql'); |  | ||||||
| const sqlInit = require('./services/sql_init'); |  | ||||||
|  |  | ||||||
| const port = normalizePort(config['Network']['port'] || '3000'); | const port = normalizePort(config['Network']['port'] || '3000'); | ||||||
| app.set('port', port); | app.set('port', port); | ||||||
| @@ -56,7 +54,7 @@ httpServer.listen(port); | |||||||
| httpServer.on('error', onError); | httpServer.on('error', onError); | ||||||
| httpServer.on('listening', onListening); | httpServer.on('listening', onListening); | ||||||
|  |  | ||||||
| sqlInit.dbReady.then(() => messagingService.init(httpServer, sessionParser)); | messagingService.init(httpServer, sessionParser); | ||||||
|  |  | ||||||
| if (utils.isElectron()) { | if (utils.isElectron()) { | ||||||
|     const electronRouting = require('./routes/electron'); |     const electronRouting = require('./routes/electron'); | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
|     <content url="file://$MODULE_DIR$"> |     <content url="file://$MODULE_DIR$"> | ||||||
|       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> |       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> | ||||||
|       <sourceFolder url="file://$MODULE_DIR$/src/public" isTestSource="false" /> |       <sourceFolder url="file://$MODULE_DIR$/src/public" isTestSource="false" /> | ||||||
|  |       <excludeFolder url="file://$MODULE_DIR$/dist" /> | ||||||
|     </content> |     </content> | ||||||
|     <orderEntry type="inheritedJdk" /> |     <orderEntry type="inheritedJdk" /> | ||||||
|     <orderEntry type="sourceFolder" forTests="false" /> |     <orderEntry type="sourceFolder" forTests="false" /> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user