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"?>
 | 
			
		||||
<dataSource name="document.db">
 | 
			
		||||
  <database-model serializer="dbm" rdbms="SQLITE" format-version="4.7">
 | 
			
		||||
    <root id="1"/>
 | 
			
		||||
  <database-model serializer="dbm" rdbms="SQLITE" format-version="4.8">
 | 
			
		||||
    <root id="1">
 | 
			
		||||
      <ServerVersion>3.16.1</ServerVersion>
 | 
			
		||||
    </root>
 | 
			
		||||
    <schema id="2" parent="1" name="main">
 | 
			
		||||
      <Current>1</Current>
 | 
			
		||||
      <Visible>1</Visible>
 | 
			
		||||
@@ -107,8 +109,7 @@
 | 
			
		||||
    <index id="35" parent="7" name="IDX_branches_noteId_parentNoteId">
 | 
			
		||||
      <ColNames>noteId
 | 
			
		||||
parentNoteId</ColNames>
 | 
			
		||||
      <ColumnCollations>
 | 
			
		||||
</ColumnCollations>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
    </index>
 | 
			
		||||
    <index id="36" parent="7" name="IDX_branches_noteId">
 | 
			
		||||
      <ColNames>noteId</ColNames>
 | 
			
		||||
@@ -142,445 +143,449 @@ parentNoteId</ColNames>
 | 
			
		||||
      <ColNames>id</ColNames>
 | 
			
		||||
      <Primary>1</Primary>
 | 
			
		||||
    </key>
 | 
			
		||||
    <foreign-key id="43" parent="8">
 | 
			
		||||
      <ColNames>noteId</ColNames>
 | 
			
		||||
      <RefTableName>notes</RefTableName>
 | 
			
		||||
      <RefColNames>noteId</RefColNames>
 | 
			
		||||
    </foreign-key>
 | 
			
		||||
    <column id="44" parent="9" name="imageId">
 | 
			
		||||
    <column id="43" parent="9" name="imageId">
 | 
			
		||||
      <Position>1</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="45" parent="9" name="format">
 | 
			
		||||
    <column id="44" parent="9" name="format">
 | 
			
		||||
      <Position>2</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="46" parent="9" name="checksum">
 | 
			
		||||
    <column id="45" parent="9" name="checksum">
 | 
			
		||||
      <Position>3</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="47" parent="9" name="name">
 | 
			
		||||
    <column id="46" parent="9" name="name">
 | 
			
		||||
      <Position>4</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="48" parent="9" name="data">
 | 
			
		||||
    <column id="47" parent="9" name="data">
 | 
			
		||||
      <Position>5</Position>
 | 
			
		||||
      <DataType>BLOB|0s</DataType>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="49" parent="9" name="isDeleted">
 | 
			
		||||
    <column id="48" parent="9" name="isDeleted">
 | 
			
		||||
      <Position>6</Position>
 | 
			
		||||
      <DataType>INT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
      <DefaultExpression>0</DefaultExpression>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="50" parent="9" name="dateModified">
 | 
			
		||||
    <column id="49" parent="9" name="dateModified">
 | 
			
		||||
      <Position>7</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="51" parent="9" name="dateCreated">
 | 
			
		||||
    <column id="50" parent="9" name="dateCreated">
 | 
			
		||||
      <Position>8</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <index id="52" parent="9" name="sqlite_autoindex_images_1">
 | 
			
		||||
    <index id="51" parent="9" name="sqlite_autoindex_images_1">
 | 
			
		||||
      <NameSurrogate>1</NameSurrogate>
 | 
			
		||||
      <ColNames>imageId</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
      <Unique>1</Unique>
 | 
			
		||||
    </index>
 | 
			
		||||
    <key id="53" parent="9">
 | 
			
		||||
    <key id="52" parent="9">
 | 
			
		||||
      <ColNames>imageId</ColNames>
 | 
			
		||||
      <Primary>1</Primary>
 | 
			
		||||
      <UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName>
 | 
			
		||||
    </key>
 | 
			
		||||
    <column id="54" parent="10" name="labelId">
 | 
			
		||||
    <column id="53" parent="10" name="labelId">
 | 
			
		||||
      <Position>1</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="55" parent="10" name="noteId">
 | 
			
		||||
    <column id="54" parent="10" name="noteId">
 | 
			
		||||
      <Position>2</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="56" parent="10" name="name">
 | 
			
		||||
    <column id="55" parent="10" name="name">
 | 
			
		||||
      <Position>3</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="57" parent="10" name="value">
 | 
			
		||||
    <column id="56" parent="10" name="value">
 | 
			
		||||
      <Position>4</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
      <DefaultExpression>''</DefaultExpression>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="58" parent="10" name="position">
 | 
			
		||||
    <column id="57" parent="10" name="position">
 | 
			
		||||
      <Position>5</Position>
 | 
			
		||||
      <DataType>INT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
      <DefaultExpression>0</DefaultExpression>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="59" parent="10" name="dateCreated">
 | 
			
		||||
    <column id="58" parent="10" name="dateCreated">
 | 
			
		||||
      <Position>6</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="60" parent="10" name="dateModified">
 | 
			
		||||
    <column id="59" parent="10" name="dateModified">
 | 
			
		||||
      <Position>7</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="61" parent="10" name="isDeleted">
 | 
			
		||||
    <column id="60" parent="10" name="isDeleted">
 | 
			
		||||
      <Position>8</Position>
 | 
			
		||||
      <DataType>INT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <index id="62" parent="10" name="sqlite_autoindex_labels_1">
 | 
			
		||||
    <index id="61" parent="10" name="sqlite_autoindex_labels_1">
 | 
			
		||||
      <NameSurrogate>1</NameSurrogate>
 | 
			
		||||
      <ColNames>labelId</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
      <Unique>1</Unique>
 | 
			
		||||
    </index>
 | 
			
		||||
    <index id="63" parent="10" name="IDX_labels_noteId">
 | 
			
		||||
    <index id="62" parent="10" name="IDX_labels_noteId">
 | 
			
		||||
      <ColNames>noteId</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
    </index>
 | 
			
		||||
    <index id="64" parent="10" name="IDX_labels_name_value">
 | 
			
		||||
    <index id="63" parent="10" name="IDX_labels_name_value">
 | 
			
		||||
      <ColNames>name
 | 
			
		||||
value</ColNames>
 | 
			
		||||
      <ColumnCollations>
 | 
			
		||||
</ColumnCollations>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
    </index>
 | 
			
		||||
    <key id="65" parent="10">
 | 
			
		||||
    <key id="64" parent="10">
 | 
			
		||||
      <ColNames>labelId</ColNames>
 | 
			
		||||
      <Primary>1</Primary>
 | 
			
		||||
      <UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName>
 | 
			
		||||
    </key>
 | 
			
		||||
    <column id="66" parent="11" name="noteImageId">
 | 
			
		||||
    <column id="65" parent="11" name="noteImageId">
 | 
			
		||||
      <Position>1</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="67" parent="11" name="noteId">
 | 
			
		||||
    <column id="66" parent="11" name="noteId">
 | 
			
		||||
      <Position>2</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="68" parent="11" name="imageId">
 | 
			
		||||
    <column id="67" parent="11" name="imageId">
 | 
			
		||||
      <Position>3</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="69" parent="11" name="isDeleted">
 | 
			
		||||
    <column id="68" parent="11" name="isDeleted">
 | 
			
		||||
      <Position>4</Position>
 | 
			
		||||
      <DataType>INT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
      <DefaultExpression>0</DefaultExpression>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="70" parent="11" name="dateModified">
 | 
			
		||||
    <column id="69" parent="11" name="dateModified">
 | 
			
		||||
      <Position>5</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="71" parent="11" name="dateCreated">
 | 
			
		||||
    <column id="70" parent="11" name="dateCreated">
 | 
			
		||||
      <Position>6</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <index id="72" parent="11" name="sqlite_autoindex_note_images_1">
 | 
			
		||||
    <index id="71" parent="11" name="sqlite_autoindex_note_images_1">
 | 
			
		||||
      <NameSurrogate>1</NameSurrogate>
 | 
			
		||||
      <ColNames>noteImageId</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
      <Unique>1</Unique>
 | 
			
		||||
    </index>
 | 
			
		||||
    <index id="73" parent="11" name="IDX_note_images_noteId_imageId">
 | 
			
		||||
    <index id="72" parent="11" name="IDX_note_images_noteId_imageId">
 | 
			
		||||
      <ColNames>noteId
 | 
			
		||||
imageId</ColNames>
 | 
			
		||||
      <ColumnCollations>
 | 
			
		||||
</ColumnCollations>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
    </index>
 | 
			
		||||
    <index id="74" parent="11" name="IDX_note_images_noteId">
 | 
			
		||||
    <index id="73" parent="11" name="IDX_note_images_noteId">
 | 
			
		||||
      <ColNames>noteId</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
    </index>
 | 
			
		||||
    <index id="75" parent="11" name="IDX_note_images_imageId">
 | 
			
		||||
    <index id="74" parent="11" name="IDX_note_images_imageId">
 | 
			
		||||
      <ColNames>imageId</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
    </index>
 | 
			
		||||
    <key id="76" parent="11">
 | 
			
		||||
    <key id="75" parent="11">
 | 
			
		||||
      <ColNames>noteImageId</ColNames>
 | 
			
		||||
      <Primary>1</Primary>
 | 
			
		||||
      <UnderlyingIndexName>sqlite_autoindex_note_images_1</UnderlyingIndexName>
 | 
			
		||||
    </key>
 | 
			
		||||
    <column id="77" parent="12" name="noteRevisionId">
 | 
			
		||||
    <column id="76" parent="12" name="noteRevisionId">
 | 
			
		||||
      <Position>1</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="78" parent="12" name="noteId">
 | 
			
		||||
    <column id="77" parent="12" name="noteId">
 | 
			
		||||
      <Position>2</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="79" parent="12" name="title">
 | 
			
		||||
    <column id="78" parent="12" name="title">
 | 
			
		||||
      <Position>3</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="80" parent="12" name="content">
 | 
			
		||||
    <column id="79" parent="12" name="content">
 | 
			
		||||
      <Position>4</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="81" parent="12" name="isProtected">
 | 
			
		||||
    <column id="80" parent="12" name="isProtected">
 | 
			
		||||
      <Position>5</Position>
 | 
			
		||||
      <DataType>INT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
      <DefaultExpression>0</DefaultExpression>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="82" parent="12" name="dateModifiedFrom">
 | 
			
		||||
    <column id="81" parent="12" name="dateModifiedFrom">
 | 
			
		||||
      <Position>6</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="83" parent="12" name="dateModifiedTo">
 | 
			
		||||
    <column id="82" parent="12" name="dateModifiedTo">
 | 
			
		||||
      <Position>7</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <index id="84" parent="12" name="sqlite_autoindex_note_revisions_1">
 | 
			
		||||
    <column id="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>
 | 
			
		||||
      <ColNames>noteRevisionId</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
      <Unique>1</Unique>
 | 
			
		||||
    </index>
 | 
			
		||||
    <index id="85" parent="12" name="IDX_note_revisions_noteId">
 | 
			
		||||
    <index id="86" parent="12" name="IDX_note_revisions_noteId">
 | 
			
		||||
      <ColNames>noteId</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
    </index>
 | 
			
		||||
    <index id="86" parent="12" name="IDX_note_revisions_dateModifiedFrom">
 | 
			
		||||
    <index id="87" parent="12" name="IDX_note_revisions_dateModifiedFrom">
 | 
			
		||||
      <ColNames>dateModifiedFrom</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
    </index>
 | 
			
		||||
    <index id="87" parent="12" name="IDX_note_revisions_dateModifiedTo">
 | 
			
		||||
    <index id="88" parent="12" name="IDX_note_revisions_dateModifiedTo">
 | 
			
		||||
      <ColNames>dateModifiedTo</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
    </index>
 | 
			
		||||
    <key id="88" parent="12">
 | 
			
		||||
    <key id="89" parent="12">
 | 
			
		||||
      <ColNames>noteRevisionId</ColNames>
 | 
			
		||||
      <Primary>1</Primary>
 | 
			
		||||
      <UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName>
 | 
			
		||||
    </key>
 | 
			
		||||
    <column id="89" parent="13" name="noteId">
 | 
			
		||||
    <column id="90" parent="13" name="noteId">
 | 
			
		||||
      <Position>1</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="90" parent="13" name="title">
 | 
			
		||||
    <column id="91" parent="13" name="title">
 | 
			
		||||
      <Position>2</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="91" parent="13" name="content">
 | 
			
		||||
    <column id="92" parent="13" name="content">
 | 
			
		||||
      <Position>3</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="92" parent="13" name="isProtected">
 | 
			
		||||
    <column id="93" parent="13" name="isProtected">
 | 
			
		||||
      <Position>4</Position>
 | 
			
		||||
      <DataType>INT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
      <DefaultExpression>0</DefaultExpression>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="93" parent="13" name="isDeleted">
 | 
			
		||||
    <column id="94" parent="13" name="isDeleted">
 | 
			
		||||
      <Position>5</Position>
 | 
			
		||||
      <DataType>INT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
      <DefaultExpression>0</DefaultExpression>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="94" parent="13" name="dateCreated">
 | 
			
		||||
    <column id="95" parent="13" name="dateCreated">
 | 
			
		||||
      <Position>6</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="95" parent="13" name="dateModified">
 | 
			
		||||
    <column id="96" parent="13" name="dateModified">
 | 
			
		||||
      <Position>7</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="96" parent="13" name="type">
 | 
			
		||||
    <column id="97" parent="13" name="type">
 | 
			
		||||
      <Position>8</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
      <DefaultExpression>'text'</DefaultExpression>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="97" parent="13" name="mime">
 | 
			
		||||
    <column id="98" parent="13" name="mime">
 | 
			
		||||
      <Position>9</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
      <DefaultExpression>'text/html'</DefaultExpression>
 | 
			
		||||
    </column>
 | 
			
		||||
    <index id="98" parent="13" name="sqlite_autoindex_notes_1">
 | 
			
		||||
    <index id="99" parent="13" name="sqlite_autoindex_notes_1">
 | 
			
		||||
      <NameSurrogate>1</NameSurrogate>
 | 
			
		||||
      <ColNames>noteId</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
      <Unique>1</Unique>
 | 
			
		||||
    </index>
 | 
			
		||||
    <index id="99" parent="13" name="IDX_notes_isDeleted">
 | 
			
		||||
    <index id="100" parent="13" name="IDX_notes_isDeleted">
 | 
			
		||||
      <ColNames>isDeleted</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
    </index>
 | 
			
		||||
    <key id="100" parent="13">
 | 
			
		||||
    <key id="101" parent="13">
 | 
			
		||||
      <ColNames>noteId</ColNames>
 | 
			
		||||
      <Primary>1</Primary>
 | 
			
		||||
      <UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName>
 | 
			
		||||
    </key>
 | 
			
		||||
    <column id="101" parent="14" name="name">
 | 
			
		||||
    <column id="102" parent="14" name="name">
 | 
			
		||||
      <Position>1</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="102" parent="14" name="value">
 | 
			
		||||
    <column id="103" parent="14" name="value">
 | 
			
		||||
      <Position>2</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="103" parent="14" name="dateModified">
 | 
			
		||||
    <column id="104" parent="14" name="dateModified">
 | 
			
		||||
      <Position>3</Position>
 | 
			
		||||
      <DataType>INT|0s</DataType>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="104" parent="14" name="isSynced">
 | 
			
		||||
    <column id="105" parent="14" name="isSynced">
 | 
			
		||||
      <Position>4</Position>
 | 
			
		||||
      <DataType>INTEGER|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
      <DefaultExpression>0</DefaultExpression>
 | 
			
		||||
    </column>
 | 
			
		||||
    <index id="105" parent="14" name="sqlite_autoindex_options_1">
 | 
			
		||||
    <index id="106" parent="14" name="sqlite_autoindex_options_1">
 | 
			
		||||
      <NameSurrogate>1</NameSurrogate>
 | 
			
		||||
      <ColNames>name</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
      <Unique>1</Unique>
 | 
			
		||||
    </index>
 | 
			
		||||
    <key id="106" parent="14">
 | 
			
		||||
    <key id="107" parent="14">
 | 
			
		||||
      <ColNames>name</ColNames>
 | 
			
		||||
      <Primary>1</Primary>
 | 
			
		||||
      <UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName>
 | 
			
		||||
    </key>
 | 
			
		||||
    <column id="107" parent="15" name="branchId">
 | 
			
		||||
    <column id="108" parent="15" name="branchId">
 | 
			
		||||
      <Position>1</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="108" parent="15" name="notePath">
 | 
			
		||||
    <column id="109" parent="15" name="notePath">
 | 
			
		||||
      <Position>2</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="109" parent="15" name="dateAccessed">
 | 
			
		||||
    <column id="110" parent="15" name="dateAccessed">
 | 
			
		||||
      <Position>3</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="110" parent="15" name="isDeleted">
 | 
			
		||||
    <column id="111" parent="15" name="isDeleted">
 | 
			
		||||
      <Position>4</Position>
 | 
			
		||||
      <DataType>INT|0s</DataType>
 | 
			
		||||
    </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>
 | 
			
		||||
      <ColNames>branchId</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
      <Unique>1</Unique>
 | 
			
		||||
    </index>
 | 
			
		||||
    <key id="112" parent="15">
 | 
			
		||||
    <key id="113" parent="15">
 | 
			
		||||
      <ColNames>branchId</ColNames>
 | 
			
		||||
      <Primary>1</Primary>
 | 
			
		||||
      <UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName>
 | 
			
		||||
    </key>
 | 
			
		||||
    <column id="113" parent="16" name="sourceId">
 | 
			
		||||
    <column id="114" parent="16" name="sourceId">
 | 
			
		||||
      <Position>1</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="114" parent="16" name="dateCreated">
 | 
			
		||||
    <column id="115" parent="16" name="dateCreated">
 | 
			
		||||
      <Position>2</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <index id="115" parent="16" name="sqlite_autoindex_source_ids_1">
 | 
			
		||||
    <index id="116" parent="16" name="sqlite_autoindex_source_ids_1">
 | 
			
		||||
      <NameSurrogate>1</NameSurrogate>
 | 
			
		||||
      <ColNames>sourceId</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
      <Unique>1</Unique>
 | 
			
		||||
    </index>
 | 
			
		||||
    <key id="116" parent="16">
 | 
			
		||||
    <key id="117" parent="16">
 | 
			
		||||
      <ColNames>sourceId</ColNames>
 | 
			
		||||
      <Primary>1</Primary>
 | 
			
		||||
      <UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName>
 | 
			
		||||
    </key>
 | 
			
		||||
    <column id="117" parent="17" name="type">
 | 
			
		||||
    <column id="118" parent="17" name="type">
 | 
			
		||||
      <Position>1</Position>
 | 
			
		||||
      <DataType>text|0s</DataType>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="118" parent="17" name="name">
 | 
			
		||||
    <column id="119" parent="17" name="name">
 | 
			
		||||
      <Position>2</Position>
 | 
			
		||||
      <DataType>text|0s</DataType>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="119" parent="17" name="tbl_name">
 | 
			
		||||
    <column id="120" parent="17" name="tbl_name">
 | 
			
		||||
      <Position>3</Position>
 | 
			
		||||
      <DataType>text|0s</DataType>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="120" parent="17" name="rootpage">
 | 
			
		||||
    <column id="121" parent="17" name="rootpage">
 | 
			
		||||
      <Position>4</Position>
 | 
			
		||||
      <DataType>integer|0s</DataType>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="121" parent="17" name="sql">
 | 
			
		||||
    <column id="122" parent="17" name="sql">
 | 
			
		||||
      <Position>5</Position>
 | 
			
		||||
      <DataType>text|0s</DataType>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="122" parent="18" name="name">
 | 
			
		||||
    <column id="123" parent="18" name="name">
 | 
			
		||||
      <Position>1</Position>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="123" parent="18" name="seq">
 | 
			
		||||
    <column id="124" parent="18" name="seq">
 | 
			
		||||
      <Position>2</Position>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="124" parent="19" name="id">
 | 
			
		||||
    <column id="125" parent="19" name="id">
 | 
			
		||||
      <Position>1</Position>
 | 
			
		||||
      <DataType>INTEGER|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
      <SequenceIdentity>1</SequenceIdentity>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="125" parent="19" name="entityName">
 | 
			
		||||
    <column id="126" parent="19" name="entityName">
 | 
			
		||||
      <Position>2</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="126" parent="19" name="entityId">
 | 
			
		||||
    <column id="127" parent="19" name="entityId">
 | 
			
		||||
      <Position>3</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="127" parent="19" name="sourceId">
 | 
			
		||||
    <column id="128" parent="19" name="sourceId">
 | 
			
		||||
      <Position>4</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <column id="128" parent="19" name="syncDate">
 | 
			
		||||
    <column id="129" parent="19" name="syncDate">
 | 
			
		||||
      <Position>5</Position>
 | 
			
		||||
      <DataType>TEXT|0s</DataType>
 | 
			
		||||
      <NotNull>1</NotNull>
 | 
			
		||||
    </column>
 | 
			
		||||
    <index id="129" parent="19" name="IDX_sync_entityName_entityId">
 | 
			
		||||
    <index id="130" parent="19" name="IDX_sync_entityName_entityId">
 | 
			
		||||
      <ColNames>entityName
 | 
			
		||||
entityId</ColNames>
 | 
			
		||||
      <ColumnCollations>
 | 
			
		||||
</ColumnCollations>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
      <Unique>1</Unique>
 | 
			
		||||
    </index>
 | 
			
		||||
    <index id="130" parent="19" name="IDX_sync_syncDate">
 | 
			
		||||
    <index id="131" parent="19" name="IDX_sync_syncDate">
 | 
			
		||||
      <ColNames>syncDate</ColNames>
 | 
			
		||||
      <ColumnCollations></ColumnCollations>
 | 
			
		||||
    </index>
 | 
			
		||||
    <key id="131" parent="19">
 | 
			
		||||
    <key id="132" parent="19">
 | 
			
		||||
      <ColNames>id</ColNames>
 | 
			
		||||
      <Primary>1</Primary>
 | 
			
		||||
    </key>
 | 
			
		||||
 
 | 
			
		||||
@@ -24,9 +24,9 @@ jq '.version = "'$VERSION'"' package.json|sponge package.json
 | 
			
		||||
 | 
			
		||||
git add package.json
 | 
			
		||||
 | 
			
		||||
echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > services/build.js
 | 
			
		||||
echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > src/services/build.js
 | 
			
		||||
 | 
			
		||||
git add services/build.js
 | 
			
		||||
git add src/services/build.js
 | 
			
		||||
 | 
			
		||||
TAG=v$VERSION
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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,
 | 
			
		||||
  PRIMARY KEY(`sourceId`)
 | 
			
		||||
);
 | 
			
		||||
CREATE TABLE IF NOT EXISTS "notes" (
 | 
			
		||||
  `noteId`	TEXT NOT NULL,
 | 
			
		||||
  `title`	TEXT,
 | 
			
		||||
  `content`	TEXT,
 | 
			
		||||
  `isProtected`	INT NOT NULL DEFAULT 0,
 | 
			
		||||
  `isDeleted`	INT NOT NULL DEFAULT 0,
 | 
			
		||||
  `dateCreated`	TEXT NOT NULL,
 | 
			
		||||
  `dateModified`	TEXT NOT NULL,
 | 
			
		||||
  type TEXT NOT NULL DEFAULT 'text',
 | 
			
		||||
  mime TEXT NOT NULL DEFAULT 'text/html',
 | 
			
		||||
  PRIMARY KEY(`noteId`)
 | 
			
		||||
);
 | 
			
		||||
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (
 | 
			
		||||
  `isDeleted`
 | 
			
		||||
);
 | 
			
		||||
CREATE TABLE IF NOT EXISTS "event_log" (
 | 
			
		||||
  `id`	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
 | 
			
		||||
  `noteId`	TEXT,
 | 
			
		||||
  `comment`	TEXT,
 | 
			
		||||
  `dateAdded`	TEXT NOT NULL,
 | 
			
		||||
  FOREIGN KEY(noteId) REFERENCES notes(noteId)
 | 
			
		||||
);
 | 
			
		||||
CREATE TABLE IF NOT EXISTS "note_revisions" (
 | 
			
		||||
  `noteRevisionId`	TEXT NOT NULL PRIMARY KEY,
 | 
			
		||||
  `noteId`	TEXT NOT NULL,
 | 
			
		||||
@@ -51,7 +29,7 @@ CREATE TABLE IF NOT EXISTS "note_revisions" (
 | 
			
		||||
  `isProtected`	INT NOT NULL DEFAULT 0,
 | 
			
		||||
  `dateModifiedFrom` TEXT NOT NULL,
 | 
			
		||||
  `dateModifiedTo` TEXT NOT NULL
 | 
			
		||||
);
 | 
			
		||||
, type TEXT DEFAULT '' NOT NULL, mime TEXT DEFAULT '' NOT NULL);
 | 
			
		||||
CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` (
 | 
			
		||||
  `noteId`
 | 
			
		||||
);
 | 
			
		||||
@@ -130,3 +108,25 @@ CREATE INDEX IDX_labels_name_value
 | 
			
		||||
  on labels (name, value);
 | 
			
		||||
CREATE INDEX IDX_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",
 | 
			
		||||
  "description": "Trilium Notes",
 | 
			
		||||
  "version": "0.10.2-beta",
 | 
			
		||||
  "version": "0.11.1",
 | 
			
		||||
  "license": "AGPL-3.0-only",
 | 
			
		||||
  "main": "electron.js",
 | 
			
		||||
  "repository": {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,13 +12,21 @@ class Note extends Entity {
 | 
			
		||||
    constructor(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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.isJson()) {
 | 
			
		||||
        this.setContent(this.content);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setContent(content) {
 | 
			
		||||
        this.content = content;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            this.jsonContent = JSON.parse(this.content);
 | 
			
		||||
        }
 | 
			
		||||
        catch(e) {}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isJson() {
 | 
			
		||||
@@ -133,7 +141,7 @@ class Note extends Entity {
 | 
			
		||||
    beforeSaving() {
 | 
			
		||||
        super.beforeSaving();
 | 
			
		||||
 | 
			
		||||
        if (this.isJson()) {
 | 
			
		||||
        if (this.isJson() && this.jsonContent) {
 | 
			
		||||
            this.content = JSON.stringify(this.jsonContent, null, '\t');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,13 @@ $list.on('change', () => {
 | 
			
		||||
    const revisionItem = revisionItems.find(r => r.noteRevisionId === optVal);
 | 
			
		||||
 | 
			
		||||
    $title.html(revisionItem.title);
 | 
			
		||||
 | 
			
		||||
    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 => {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,10 @@ class Branch {
 | 
			
		||||
        return await this.treeCache.getNote(this.noteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isTopLevel() {
 | 
			
		||||
        return this.parentNoteId === 'root';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get toString() {
 | 
			
		||||
        return `Branch(branchId=${this.branchId})`;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,12 @@ class NoteFull extends NoteShort {
 | 
			
		||||
 | 
			
		||||
        this.content = row.content;
 | 
			
		||||
 | 
			
		||||
        if (this.isJson()) {
 | 
			
		||||
        if (this.content !== "" && this.isJson()) {
 | 
			
		||||
            try {
 | 
			
		||||
                this.jsonContent = JSON.parse(this.content);
 | 
			
		||||
            }
 | 
			
		||||
            catch(e) {}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,10 @@ class NoteShort {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getChildBranches() {
 | 
			
		||||
        if (!this.treeCache.children[this.noteId]) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const branches = [];
 | 
			
		||||
 | 
			
		||||
        for (const child of this.treeCache.children[this.noteId]) {
 | 
			
		||||
@@ -44,6 +48,14 @@ class NoteShort {
 | 
			
		||||
    get toString() {
 | 
			
		||||
        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;
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import treeCache from "./tree_cache.js";
 | 
			
		||||
import treeUtils from "./tree_utils.js";
 | 
			
		||||
import protectedSessionHolder from './protected_session_holder.js';
 | 
			
		||||
 | 
			
		||||
async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
 | 
			
		||||
    if (!parentNoteId) {
 | 
			
		||||
@@ -21,9 +22,6 @@ async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
 | 
			
		||||
        titlePath = '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // https://github.com/zadam/trilium/issues/46
 | 
			
		||||
    // unfortunately not easy to implement because we don't have an easy access to note's isProtected property
 | 
			
		||||
 | 
			
		||||
    const autocompleteItems = [];
 | 
			
		||||
 | 
			
		||||
    for (const childNote of childNotes) {
 | 
			
		||||
@@ -34,10 +32,12 @@ async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
 | 
			
		||||
        const childNotePath = (notePath ? (notePath + '/') : '') + childNote.noteId;
 | 
			
		||||
        const childTitlePath = (titlePath ? (titlePath + ' / ') : '') + await treeUtils.getNoteTitle(childNote.noteId, parentNoteId);
 | 
			
		||||
 | 
			
		||||
        if (!childNote.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) {
 | 
			
		||||
            autocompleteItems.push({
 | 
			
		||||
                value: childTitlePath + ' (' + childNotePath + ')',
 | 
			
		||||
                label: childTitlePath
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const childItems = await getAutocompleteItems(childNote.noteId, childNotePath, childTitlePath);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import treeService from './tree.js';
 | 
			
		||||
import treeUtils from './tree_utils.js';
 | 
			
		||||
import noteTypeService from './note_type.js';
 | 
			
		||||
import protectedSessionService from './protected_session.js';
 | 
			
		||||
import protectedSessionHolder from './protected_session_holder.js';
 | 
			
		||||
@@ -24,6 +25,7 @@ const $noteDetailWrapper = $("#note-detail-wrapper");
 | 
			
		||||
const $noteIdDisplay = $("#note-id-display");
 | 
			
		||||
const $labelList = $("#label-list");
 | 
			
		||||
const $labelListInner = $("#label-list-inner");
 | 
			
		||||
const $childrenOverview = $("#children-overview");
 | 
			
		||||
 | 
			
		||||
let currentNote = null;
 | 
			
		||||
 | 
			
		||||
@@ -73,50 +75,42 @@ function noteChanged() {
 | 
			
		||||
async function reload() {
 | 
			
		||||
    // no saving here
 | 
			
		||||
 | 
			
		||||
    await loadNoteToEditor(getCurrentNoteId());
 | 
			
		||||
    await loadNoteDetail(getCurrentNoteId());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function switchToNote(noteId) {
 | 
			
		||||
    if (getCurrentNoteId() !== noteId) {
 | 
			
		||||
        await saveNoteIfChanged();
 | 
			
		||||
 | 
			
		||||
        await loadNoteToEditor(noteId);
 | 
			
		||||
        await loadNoteDetail(noteId);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function saveNote() {
 | 
			
		||||
    const note = getCurrentNote();
 | 
			
		||||
 | 
			
		||||
    note.title = $noteTitle.val();
 | 
			
		||||
    note.content = getComponent(note.type).getContent();
 | 
			
		||||
 | 
			
		||||
    treeService.setNoteTitle(note.noteId, note.title);
 | 
			
		||||
 | 
			
		||||
    await server.put('notes/' + note.noteId, note.dto);
 | 
			
		||||
 | 
			
		||||
    isNoteChanged = false;
 | 
			
		||||
 | 
			
		||||
    if (note.isProtected) {
 | 
			
		||||
        protectedSessionHolder.touchProtectedSession();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    infoService.showMessage("Saved!");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function saveNoteIfChanged() {
 | 
			
		||||
    if (!isNoteChanged) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const note = getCurrentNote();
 | 
			
		||||
 | 
			
		||||
    updateNoteFromInputs(note);
 | 
			
		||||
 | 
			
		||||
    await saveNoteToServer(note);
 | 
			
		||||
 | 
			
		||||
    if (note.isProtected) {
 | 
			
		||||
        protectedSessionHolder.touchProtectedSession();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateNoteFromInputs(note) {
 | 
			
		||||
    note.title = $noteTitle.val();
 | 
			
		||||
    note.content = getComponent(note.type).getContent();
 | 
			
		||||
 | 
			
		||||
    treeService.setNoteTitle(note.noteId, note.title);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function saveNoteToServer(note) {
 | 
			
		||||
    const dto = Object.assign({}, note);
 | 
			
		||||
    delete dto.treeCache;
 | 
			
		||||
    delete dto.hideInAutocomplete;
 | 
			
		||||
 | 
			
		||||
    await server.put('notes/' + dto.noteId, dto);
 | 
			
		||||
 | 
			
		||||
    isNoteChanged = false;
 | 
			
		||||
 | 
			
		||||
    infoService.showMessage("Saved!");
 | 
			
		||||
    await saveNote();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setNoteBackgroundIfProtected(note) {
 | 
			
		||||
@@ -145,7 +139,7 @@ async function handleProtectedSession() {
 | 
			
		||||
    protectedSessionService.ensureDialogIsClosed();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function loadNoteToEditor(noteId) {
 | 
			
		||||
async function loadNoteDetail(noteId) {
 | 
			
		||||
    currentNote = await loadNote(noteId);
 | 
			
		||||
 | 
			
		||||
    if (isNewNoteCreated) {
 | 
			
		||||
@@ -182,7 +176,35 @@ async function loadNoteToEditor(noteId) {
 | 
			
		||||
    // after loading new note make sure editor is scrolled to the top
 | 
			
		||||
    $noteDetailWrapper.scrollTop(0);
 | 
			
		||||
 | 
			
		||||
    await loadLabelList();
 | 
			
		||||
    const labels = await loadLabelList();
 | 
			
		||||
 | 
			
		||||
    const hideChildrenOverview = labels.some(label => label.name === 'hideChildrenOverview');
 | 
			
		||||
    await showChildrenOverview(hideChildrenOverview);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function showChildrenOverview(hideChildrenOverview) {
 | 
			
		||||
    if (hideChildrenOverview) {
 | 
			
		||||
        $childrenOverview.hide();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const note = getCurrentNote();
 | 
			
		||||
 | 
			
		||||
    $childrenOverview.empty();
 | 
			
		||||
 | 
			
		||||
    const notePath = treeService.getCurrentNotePath();
 | 
			
		||||
 | 
			
		||||
    for (const childBranch of await note.getChildBranches()) {
 | 
			
		||||
        const link = $('<a>', {
 | 
			
		||||
            href: 'javascript:',
 | 
			
		||||
            text: await treeUtils.getNoteTitle(childBranch.noteId, childBranch.parentNoteId)
 | 
			
		||||
        }).attr('action', 'note').attr('note-path', notePath + '/' + childBranch.noteId);
 | 
			
		||||
 | 
			
		||||
        const childEl = $('<div class="child-overview">').html(link);
 | 
			
		||||
        $childrenOverview.append(childEl);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $childrenOverview.show();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function loadLabelList() {
 | 
			
		||||
@@ -202,6 +224,8 @@ async function loadLabelList() {
 | 
			
		||||
    else {
 | 
			
		||||
        $labelList.hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return labels;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function loadNote(noteId) {
 | 
			
		||||
@@ -245,8 +269,6 @@ setInterval(saveNoteIfChanged, 5000);
 | 
			
		||||
export default {
 | 
			
		||||
    reload,
 | 
			
		||||
    switchToNote,
 | 
			
		||||
    updateNoteFromInputs,
 | 
			
		||||
    saveNoteToServer,
 | 
			
		||||
    setNoteBackgroundIfProtected,
 | 
			
		||||
    loadNote,
 | 
			
		||||
    getCurrentNote,
 | 
			
		||||
@@ -255,6 +277,7 @@ export default {
 | 
			
		||||
    newNoteCreated,
 | 
			
		||||
    focus,
 | 
			
		||||
    loadLabelList,
 | 
			
		||||
    saveNote,
 | 
			
		||||
    saveNoteIfChanged,
 | 
			
		||||
    noteChanged
 | 
			
		||||
};
 | 
			
		||||
@@ -16,6 +16,10 @@ async function show() {
 | 
			
		||||
        CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
 | 
			
		||||
        CodeMirror.keyMap.default["Tab"] = "indentMore";
 | 
			
		||||
 | 
			
		||||
        // these conflict with backward/forward navigation shortcuts
 | 
			
		||||
        delete CodeMirror.keyMap.default["Alt-Left"];
 | 
			
		||||
        delete CodeMirror.keyMap.default["Alt-Right"];
 | 
			
		||||
 | 
			
		||||
        CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js';
 | 
			
		||||
 | 
			
		||||
        codeEditor = CodeMirror($noteDetailCode[0], {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import treeService from './tree.js';
 | 
			
		||||
import noteDetail from './note_detail.js';
 | 
			
		||||
import noteDetailService from './note_detail.js';
 | 
			
		||||
import server from './server.js';
 | 
			
		||||
import infoService from "./info.js";
 | 
			
		||||
 | 
			
		||||
@@ -84,13 +84,13 @@ function NoteTypeModel() {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    async function save() {
 | 
			
		||||
        const note = noteDetail.getCurrentNote();
 | 
			
		||||
        const note = noteDetailService.getCurrentNote();
 | 
			
		||||
 | 
			
		||||
        await server.put('notes/' + note.noteId
 | 
			
		||||
            + '/type/' + encodeURIComponent(self.type())
 | 
			
		||||
            + '/mime/' + encodeURIComponent(self.mime()));
 | 
			
		||||
 | 
			
		||||
        await noteDetail.reload();
 | 
			
		||||
        await noteDetailService.reload();
 | 
			
		||||
 | 
			
		||||
        // for the note icon to be updated in the tree
 | 
			
		||||
        await treeService.reload();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import treeService from './tree.js';
 | 
			
		||||
import noteDetail from './note_detail.js';
 | 
			
		||||
import noteDetailService from './note_detail.js';
 | 
			
		||||
import utils from './utils.js';
 | 
			
		||||
import server from './server.js';
 | 
			
		||||
import protectedSessionHolder from './protected_session_holder.js';
 | 
			
		||||
@@ -57,7 +57,7 @@ async function setupProtectedSession() {
 | 
			
		||||
 | 
			
		||||
    $dialog.dialog("close");
 | 
			
		||||
 | 
			
		||||
    noteDetail.reload();
 | 
			
		||||
    noteDetailService.reload();
 | 
			
		||||
    treeService.reload();
 | 
			
		||||
 | 
			
		||||
    if (protectedSessionDeferred !== null) {
 | 
			
		||||
@@ -90,33 +90,27 @@ async function enterProtectedSession(password) {
 | 
			
		||||
async function protectNoteAndSendToServer() {
 | 
			
		||||
    await ensureProtectedSession(true, true);
 | 
			
		||||
 | 
			
		||||
    const note = noteDetail.getCurrentNote();
 | 
			
		||||
 | 
			
		||||
    noteDetail.updateNoteFromInputs(note);
 | 
			
		||||
 | 
			
		||||
    const note = noteDetailService.getCurrentNote();
 | 
			
		||||
    note.isProtected = true;
 | 
			
		||||
 | 
			
		||||
    await noteDetail.saveNoteToServer(note);
 | 
			
		||||
    await noteDetailService.saveNote(note);
 | 
			
		||||
 | 
			
		||||
    treeService.setProtected(note.noteId, note.isProtected);
 | 
			
		||||
 | 
			
		||||
    noteDetail.setNoteBackgroundIfProtected(note);
 | 
			
		||||
    noteDetailService.setNoteBackgroundIfProtected(note);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function unprotectNoteAndSendToServer() {
 | 
			
		||||
    await ensureProtectedSession(true, true);
 | 
			
		||||
 | 
			
		||||
    const note = noteDetail.getCurrentNote();
 | 
			
		||||
 | 
			
		||||
    noteDetail.updateNoteFromInputs(note);
 | 
			
		||||
 | 
			
		||||
    const note = noteDetailService.getCurrentNote();
 | 
			
		||||
    note.isProtected = false;
 | 
			
		||||
 | 
			
		||||
    await noteDetail.saveNoteToServer(note);
 | 
			
		||||
    await noteDetailService.saveNote(note);
 | 
			
		||||
 | 
			
		||||
    treeService.setProtected(note.noteId, note.isProtected);
 | 
			
		||||
 | 
			
		||||
    noteDetail.setNoteBackgroundIfProtected(note);
 | 
			
		||||
    noteDetailService.setNoteBackgroundIfProtected(note);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function protectBranch(noteId, protect) {
 | 
			
		||||
@@ -127,7 +121,7 @@ async function protectBranch(noteId, protect) {
 | 
			
		||||
    infoService.showMessage("Request to un/protect sub tree has finished successfully");
 | 
			
		||||
 | 
			
		||||
    treeService.reload();
 | 
			
		||||
    noteDetail.reload();
 | 
			
		||||
    noteDetailService.reload();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$passwordForm.submit(() => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import treeService from './tree.js';
 | 
			
		||||
import server from './server.js';
 | 
			
		||||
import utils from './utils.js';
 | 
			
		||||
import infoService from './info.js';
 | 
			
		||||
 | 
			
		||||
function ScriptApi(startNote, currentNote) {
 | 
			
		||||
    const $pluginButtons = $("#plugin-buttons");
 | 
			
		||||
@@ -56,8 +57,8 @@ function ScriptApi(startNote, currentNote) {
 | 
			
		||||
        runOnServer,
 | 
			
		||||
        formatDateISO: utils.formatDateISO,
 | 
			
		||||
        parseDate: utils.parseDate,
 | 
			
		||||
        showMessage: utils.showMessage,
 | 
			
		||||
        showError: utils.showError,
 | 
			
		||||
        showMessage: infoService.showMessage,
 | 
			
		||||
        showError: infoService.showError,
 | 
			
		||||
        reloadTree: treeService.reload
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -293,7 +293,7 @@ function initFancyTree(branch) {
 | 
			
		||||
        keyboard: false, // we takover keyboard handling in the hotkeys plugin
 | 
			
		||||
        extensions: ["hotkeys", "filter", "dnd", "clones"],
 | 
			
		||||
        source: branch,
 | 
			
		||||
        scrollParent: $("#tree"),
 | 
			
		||||
        scrollParent: $tree,
 | 
			
		||||
        click: (event, data) => {
 | 
			
		||||
            const targetType = data.targetType;
 | 
			
		||||
            const node = data.node;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										793
									
								
								src/public/libraries/jquery.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										793
									
								
								src/public/libraries/jquery.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -5,9 +5,9 @@
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-areas: "header header"
 | 
			
		||||
                         "tree-actions title"
 | 
			
		||||
                         "search note-content"
 | 
			
		||||
                         "tree note-content"
 | 
			
		||||
                         "parent-list note-content"
 | 
			
		||||
                         "search note-detail"
 | 
			
		||||
                         "tree note-detail"
 | 
			
		||||
                         "parent-list note-detail"
 | 
			
		||||
                         "parent-list label-list";
 | 
			
		||||
    grid-template-columns: 2fr 5fr;
 | 
			
		||||
    grid-template-rows: auto
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
    border: 0 !important;
 | 
			
		||||
    box-shadow: none !important;
 | 
			
		||||
    /* 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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -249,7 +249,7 @@ div.ui-tooltip {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#note-detail-code {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    min-height: 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.CodeMirror {
 | 
			
		||||
@@ -289,3 +289,35 @@ div.ui-tooltip {
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    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,50 +13,31 @@ async function exportNote(req, res) {
 | 
			
		||||
 | 
			
		||||
    const pack = tar.pack();
 | 
			
		||||
 | 
			
		||||
    const name = await exportNoteInner(branchId, '', pack);
 | 
			
		||||
    const exportedNoteIds = [];
 | 
			
		||||
    const name = await exportNoteInner(branchId, '');
 | 
			
		||||
 | 
			
		||||
    pack.finalize();
 | 
			
		||||
 | 
			
		||||
    res.setHeader('Content-Disposition', 'file; filename="' + name + '.tar"');
 | 
			
		||||
    res.setHeader('Content-Type', 'application/tar');
 | 
			
		||||
 | 
			
		||||
    pack.pipe(res);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function exportNoteInner(branchId, directory, pack) {
 | 
			
		||||
    async function exportNoteInner(branchId, directory) {
 | 
			
		||||
        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 {
 | 
			
		||||
        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 => {
 | 
			
		||||
@@ -66,6 +47,41 @@ async function getMetadata(note) {
 | 
			
		||||
                };
 | 
			
		||||
            })
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        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();
 | 
			
		||||
 | 
			
		||||
    res.setHeader('Content-Disposition', 'file; filename="' + name + '.tar"');
 | 
			
		||||
    res.setHeader('Content-Type', 'application/tar');
 | 
			
		||||
 | 
			
		||||
    pack.pipe(res);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
const repository = require('../../services/repository');
 | 
			
		||||
const labelService = require('../../services/labels');
 | 
			
		||||
const noteService = require('../../services/notes');
 | 
			
		||||
const Branch = require('../../entities/branch');
 | 
			
		||||
const tar = require('tar-stream');
 | 
			
		||||
const stream = require('stream');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
@@ -31,7 +32,7 @@ async function parseImportFile(file) {
 | 
			
		||||
    const extract = tar.extract();
 | 
			
		||||
 | 
			
		||||
    extract.on('entry', function(header, stream, next) {
 | 
			
		||||
        let {name, key} = getFileName(header.name);
 | 
			
		||||
        const {name, key} = getFileName(header.name);
 | 
			
		||||
 | 
			
		||||
        let file = fileMap[name];
 | 
			
		||||
 | 
			
		||||
@@ -97,30 +98,46 @@ async function importTar(req) {
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
        if (file.meta.version !== 1) {
 | 
			
		||||
            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') {
 | 
			
		||||
            file.data = file.data.toString("UTF-8");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const {note} = await noteService.createNote(parentNoteId, file.meta.title, file.data, {
 | 
			
		||||
            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) {
 | 
			
		||||
            await labelService.createLabel(note.noteId, label.name, label.value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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() {
 | 
			
		||||
    return {
 | 
			
		||||
        'hashes': await contentHashService.getHashes(),
 | 
			
		||||
        'max_sync_id': await sql.getValue('SELECT MAX(id) FROM sync')
 | 
			
		||||
        hashes: await contentHashService.getHashes(),
 | 
			
		||||
        maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync')
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -58,126 +58,18 @@ async function forceNoteSync(req) {
 | 
			
		||||
async function getChanged(req) {
 | 
			
		||||
    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) {
 | 
			
		||||
    const noteId = req.params.noteId;
 | 
			
		||||
    const entity = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
 | 
			
		||||
async function update(req) {
 | 
			
		||||
    const sourceId = req.body.sourceId;
 | 
			
		||||
    const entities = req.body.entities;
 | 
			
		||||
 | 
			
		||||
    syncService.serializeNoteContentBuffer(entity);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        entity: entity
 | 
			
		||||
    };
 | 
			
		||||
    for (const {sync, entity} of entities) {
 | 
			
		||||
        await syncUpdateService.updateEntity(sync.entityName, entity, sourceId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
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 = {
 | 
			
		||||
@@ -187,24 +79,5 @@ module.exports = {
 | 
			
		||||
    forceFullSync,
 | 
			
		||||
    forceNoteSync,
 | 
			
		||||
    getChanged,
 | 
			
		||||
    getNote,
 | 
			
		||||
    getBranch,
 | 
			
		||||
    getImage,
 | 
			
		||||
    getNoteImage,
 | 
			
		||||
    getNoteReordering,
 | 
			
		||||
    getNoteRevision,
 | 
			
		||||
    getRecentNote,
 | 
			
		||||
    getOption,
 | 
			
		||||
    getLabel,
 | 
			
		||||
    getApiToken,
 | 
			
		||||
    updateNote,
 | 
			
		||||
    updateBranch,
 | 
			
		||||
    updateImage,
 | 
			
		||||
    updateNoteImage,
 | 
			
		||||
    updateNoteReordering,
 | 
			
		||||
    updateNoteRevision,
 | 
			
		||||
    updateRecentNote,
 | 
			
		||||
    updateOption,
 | 
			
		||||
    updateLabel,
 | 
			
		||||
    updateApiToken
 | 
			
		||||
    update
 | 
			
		||||
};
 | 
			
		||||
@@ -147,26 +147,7 @@ function register(app) {
 | 
			
		||||
    apiRoute(POST, '/api/sync/force-full-sync', syncApiRoute.forceFullSync);
 | 
			
		||||
    apiRoute(POST, '/api/sync/force-note-sync/:noteId', syncApiRoute.forceNoteSync);
 | 
			
		||||
    apiRoute(GET, '/api/sync/changed', syncApiRoute.getChanged);
 | 
			
		||||
    apiRoute(GET, '/api/sync/notes/:noteId', syncApiRoute.getNote);
 | 
			
		||||
    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(PUT, '/api/sync/update', syncApiRoute.update);
 | 
			
		||||
 | 
			
		||||
    apiRoute(GET, '/api/event-log', eventLogRoute.getEventLog);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
const build = require('./build');
 | 
			
		||||
const packageJson = require('../../package');
 | 
			
		||||
 | 
			
		||||
const APP_DB_VERSION = 86;
 | 
			
		||||
const APP_DB_VERSION = 88;
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    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 utils = require('./utils');
 | 
			
		||||
const log = require('./log');
 | 
			
		||||
const eventLogService = require('./event_log');
 | 
			
		||||
const messagingService = require('./messaging');
 | 
			
		||||
 | 
			
		||||
function getHash(rows) {
 | 
			
		||||
    let hash = '';
 | 
			
		||||
@@ -121,6 +125,29 @@ async function getHashes() {
 | 
			
		||||
    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 = {
 | 
			
		||||
    getHashes
 | 
			
		||||
    getHashes,
 | 
			
		||||
    checkContentHashes
 | 
			
		||||
};
 | 
			
		||||
@@ -11,7 +11,8 @@ const BUILTIN_LABELS = [
 | 
			
		||||
    'run',
 | 
			
		||||
    'manualTransactionHandling',
 | 
			
		||||
    'disableInclusion',
 | 
			
		||||
    'appCss'
 | 
			
		||||
    'appCss',
 | 
			
		||||
    'hideChildrenOverview'
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
async function getNotesWithLabel(name, value) {
 | 
			
		||||
 
 | 
			
		||||
@@ -15,14 +15,22 @@ const logger = require('simple-node-logger').createRollingFileLogger({
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function info(message) {
 | 
			
		||||
    logger.info(message);
 | 
			
		||||
 | 
			
		||||
    // info messages are logged asynchronously
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        console.log(message);
 | 
			
		||||
 | 
			
		||||
        logger.info(message);
 | 
			
		||||
    }, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function error(message) {
 | 
			
		||||
    message = "ERROR: " + message;
 | 
			
		||||
 | 
			
		||||
    // 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" ];
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,7 @@ async function createNewNote(parentNoteId, noteData) {
 | 
			
		||||
        noteId: note.noteId,
 | 
			
		||||
        parentNoteId: parentNoteId,
 | 
			
		||||
        notePosition: newNotePos,
 | 
			
		||||
        prefix: noteData.prefix,
 | 
			
		||||
        isExpanded: 0
 | 
			
		||||
    }).save();
 | 
			
		||||
 | 
			
		||||
@@ -180,6 +181,8 @@ async function saveNoteRevision(note) {
 | 
			
		||||
            // title and text should be decrypted now
 | 
			
		||||
            title: note.title,
 | 
			
		||||
            content: note.content,
 | 
			
		||||
            type: note.type,
 | 
			
		||||
            mime: note.mime,
 | 
			
		||||
            isProtected: 0, // will be fixed in the protectNoteRevisions() call
 | 
			
		||||
            dateModifiedFrom: note.dateModified,
 | 
			
		||||
            dateModifiedTo: dateUtils.nowDate()
 | 
			
		||||
@@ -198,7 +201,7 @@ async function updateNote(noteId, noteUpdates) {
 | 
			
		||||
    await saveNoteRevision(note);
 | 
			
		||||
 | 
			
		||||
    note.title = noteUpdates.title;
 | 
			
		||||
    note.content = noteUpdates.content;
 | 
			
		||||
    note.setContent(noteUpdates.content);
 | 
			
		||||
    note.isProtected = noteUpdates.isProtected;
 | 
			
		||||
    await note.save();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -149,11 +149,13 @@ async function transactional(func) {
 | 
			
		||||
            resolve();
 | 
			
		||||
        }
 | 
			
		||||
        catch (e) {
 | 
			
		||||
            if (transactionActive) {
 | 
			
		||||
                log.error("Error executing transaction, executing rollback. Inner exception: " + e.stack + error.stack);
 | 
			
		||||
 | 
			
		||||
                await rollback();
 | 
			
		||||
 | 
			
		||||
                transactionActive = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            reject(e);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -10,10 +10,8 @@ const sourceIdService = require('./source_id');
 | 
			
		||||
const dateUtils = require('./date_utils');
 | 
			
		||||
const syncUpdateService = require('./sync_update');
 | 
			
		||||
const contentHashService = require('./content_hash');
 | 
			
		||||
const eventLogService = require('./event_log');
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const appInfo = require('./app_info');
 | 
			
		||||
const messagingService = require('./messaging');
 | 
			
		||||
const syncSetup = require('./sync_setup');
 | 
			
		||||
const syncMutexService = require('./sync_mutex');
 | 
			
		||||
const cls = require('./cls');
 | 
			
		||||
@@ -91,69 +89,19 @@ async function login() {
 | 
			
		||||
    return syncContext;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getLastSyncedPull() {
 | 
			
		||||
    return parseInt(await optionService.getOption('lastSyncedPull'));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function setLastSyncedPull(syncId) {
 | 
			
		||||
    await optionService.setOption('lastSyncedPull', syncId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 of syncRows) {
 | 
			
		||||
    for (const {sync, entity} of rows) {
 | 
			
		||||
        if (sourceIdService.isLocalSourceId(sync.sourceId)) {
 | 
			
		||||
            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 {
 | 
			
		||||
            throw new Error(`Unrecognized entity type ${sync.entityName} in sync #${sync.id}`);
 | 
			
		||||
            await syncUpdateService.updateEntity(sync.entityName, entity, syncContext.sourceId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await setLastSyncedPull(sync.id);
 | 
			
		||||
@@ -162,145 +110,69 @@ async function pullSync(syncContext) {
 | 
			
		||||
    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) {
 | 
			
		||||
    let lastSyncedPush = await getLastSyncedPush();
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
            // nothing to sync
 | 
			
		||||
        const filteredSyncs = syncs.filter(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");
 | 
			
		||||
 | 
			
		||||
            await setLastSyncedPush(lastSyncedPush);
 | 
			
		||||
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (sync.sourceId === syncContext.sourceId) {
 | 
			
		||||
            log.info(`Skipping push #${sync.id} ${sync.entityName} ${sync.entityId} because it originates from sync target`);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            await pushEntity(sync, syncContext);
 | 
			
		||||
        }
 | 
			
		||||
        const syncRecords = await getSyncRecords(filteredSyncs);
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
    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.");
 | 
			
		||||
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const lastSyncedPush = await getLastSyncedPush();
 | 
			
		||||
    const notPushedSyncs = await sql.getValue("SELECT COUNT(*) FROM sync WHERE id > ?", [lastSyncedPush]);
 | 
			
		||||
    const notPushedSyncs = await sql.getValue("SELECT COUNT(*) FROM sync WHERE id > ?", [await getLastSyncedPush()]);
 | 
			
		||||
 | 
			
		||||
    if (notPushedSyncs > 0) {
 | 
			
		||||
        log.info("There's " + notPushedSyncs + " outstanding pushes, skipping content check.");
 | 
			
		||||
        log.info(`There's ${notPushedSyncs} outstanding pushes, skipping content check.`);
 | 
			
		||||
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const hashes = await contentHashService.getHashes();
 | 
			
		||||
    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");
 | 
			
		||||
    }
 | 
			
		||||
    await contentHashService.checkContentHashes(resp.hashes);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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(() => {
 | 
			
		||||
    if (syncSetup.isSyncSetup) {
 | 
			
		||||
        log.info("Setting up sync to " + syncSetup.SYNC_SERVER + " with timeout " + syncSetup.SYNC_TIMEOUT);
 | 
			
		||||
@@ -357,5 +303,5 @@ sqlInit.dbReady.then(() => {
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    sync,
 | 
			
		||||
    serializeNoteContentBuffer
 | 
			
		||||
    getSyncRecords
 | 
			
		||||
};
 | 
			
		||||
@@ -91,6 +91,8 @@ async function fillSyncRows(entityName, entityKey) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function fillAllSyncRows() {
 | 
			
		||||
    await sql.execute("DELETE FROM sync");
 | 
			
		||||
 | 
			
		||||
    await fillSyncRows("notes", "noteId");
 | 
			
		||||
    await fillSyncRows("branches", "branchId");
 | 
			
		||||
    await fillSyncRows("note_revisions", "noteRevisionId");
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,42 @@ const log = require('./log');
 | 
			
		||||
const eventLogService = require('./event_log');
 | 
			
		||||
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) {
 | 
			
		||||
    if (note.type === 'file') {
 | 
			
		||||
        note.content = new Buffer(note.content, 'binary');
 | 
			
		||||
@@ -159,14 +195,5 @@ async function updateApiToken(entity, sourceId) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    updateNote,
 | 
			
		||||
    updateBranch,
 | 
			
		||||
    updateNoteRevision,
 | 
			
		||||
    updateNoteReordering,
 | 
			
		||||
    updateOptions,
 | 
			
		||||
    updateRecentNotes,
 | 
			
		||||
    updateImage,
 | 
			
		||||
    updateNoteImage,
 | 
			
		||||
    updateLabel,
 | 
			
		||||
    updateApiToken
 | 
			
		||||
    updateEntity
 | 
			
		||||
};
 | 
			
		||||
@@ -132,8 +132,9 @@
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div style="position: relative; overflow: auto; grid-area: note-content; padding-left: 10px; padding-top: 10px;" id="note-detail-wrapper">
 | 
			
		||||
        <div id="note-detail-text" class="note-detail-component"></div>
 | 
			
		||||
      <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 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 style="display: flex; align-items: center;">
 | 
			
		||||
@@ -204,6 +205,9 @@
 | 
			
		||||
          <input type="file" id="file-upload" style="display: none" />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div id="children-overview"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div id="label-list">
 | 
			
		||||
        <button class="btn btn-sm show-labels-button">Labels:</button>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user