mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 03:46:37 +01:00 
			
		
		
		
	Compare commits
	
		
			26 Commits
		
	
	
		
			v0.10.2-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 | 
@@ -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`
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "trilium",
 | 
					  "name": "trilium",
 | 
				
			||||||
  "description": "Trilium Notes",
 | 
					  "description": "Trilium Notes",
 | 
				
			||||||
  "version": "0.10.2-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,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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -16,6 +16,10 @@ async function show() {
 | 
				
			|||||||
        CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
 | 
					        CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
 | 
				
			||||||
        CodeMirror.keyMap.default["Tab"] = "indentMore";
 | 
					        CodeMirror.keyMap.default["Tab"] = "indentMore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // these conflict with backward/forward navigation shortcuts
 | 
				
			||||||
 | 
					        delete CodeMirror.keyMap.default["Alt-Left"];
 | 
				
			||||||
 | 
					        delete CodeMirror.keyMap.default["Alt-Right"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js';
 | 
					        CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        codeEditor = CodeMirror($noteDetailCode[0], {
 | 
					        codeEditor = CodeMirror($noteDetailCode[0], {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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");
 | 
				
			||||||
@@ -56,8 +57,8 @@ function ScriptApi(startNote, currentNote) {
 | 
				
			|||||||
        runOnServer,
 | 
					        runOnServer,
 | 
				
			||||||
        formatDateISO: utils.formatDateISO,
 | 
					        formatDateISO: utils.formatDateISO,
 | 
				
			||||||
        parseDate: utils.parseDate,
 | 
					        parseDate: utils.parseDate,
 | 
				
			||||||
        showMessage: utils.showMessage,
 | 
					        showMessage: infoService.showMessage,
 | 
				
			||||||
        showError: utils.showError,
 | 
					        showError: infoService.showError,
 | 
				
			||||||
        reloadTree: treeService.reload
 | 
					        reloadTree: treeService.reload
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
											
										
									
								
							@@ -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);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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')
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -58,126 +58,18 @@ async function forceNoteSync(req) {
 | 
				
			|||||||
async function getChanged(req) {
 | 
					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
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -147,26 +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/branches', syncApiRoute.updateBranch);
 | 
					 | 
				
			||||||
    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" };
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -11,7 +11,8 @@ const BUILTIN_LABELS = [
 | 
				
			|||||||
    'run',
 | 
					    'run',
 | 
				
			||||||
    'manualTransactionHandling',
 | 
					    'manualTransactionHandling',
 | 
				
			||||||
    'disableInclusion',
 | 
					    'disableInclusion',
 | 
				
			||||||
    'appCss'
 | 
					    'appCss',
 | 
				
			||||||
 | 
					    'hideChildrenOverview'
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function getNotesWithLabel(name, value) {
 | 
					async function getNotesWithLabel(name, value) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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" ];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -149,11 +149,13 @@ async function transactional(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);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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');
 | 
				
			||||||
@@ -159,14 +195,5 @@ async function updateApiToken(entity, sourceId) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    updateNote,
 | 
					    updateEntity
 | 
				
			||||||
    updateBranch,
 | 
					 | 
				
			||||||
    updateNoteRevision,
 | 
					 | 
				
			||||||
    updateNoteReordering,
 | 
					 | 
				
			||||||
    updateOptions,
 | 
					 | 
				
			||||||
    updateRecentNotes,
 | 
					 | 
				
			||||||
    updateImage,
 | 
					 | 
				
			||||||
    updateNoteImage,
 | 
					 | 
				
			||||||
    updateLabel,
 | 
					 | 
				
			||||||
    updateApiToken
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -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">
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user