#126, added skeleton of note relations, copied from similar concept of labels

This commit is contained in:
azivner 2018-07-27 10:52:48 +02:00
parent 4d6eda8fe6
commit 8a95afd756
15 changed files with 662 additions and 129 deletions

View File

@ -21,533 +21,529 @@
<table id="13" parent="2" name="notes"/>
<table id="14" parent="2" name="options"/>
<table id="15" parent="2" name="recent_notes"/>
<table id="16" parent="2" name="source_ids"/>
<table id="17" parent="2" name="sqlite_master">
<table id="16" parent="2" name="relations"/>
<table id="17" parent="2" name="source_ids"/>
<table id="18" parent="2" name="sqlite_master">
<System>1</System>
</table>
<table id="18" parent="2" name="sqlite_sequence">
<table id="19" parent="2" name="sqlite_sequence">
<System>1</System>
</table>
<table id="19" parent="2" name="sync"/>
<column id="20" parent="6" name="apiTokenId">
<table id="20" parent="2" name="sync"/>
<column id="21" parent="6" name="apiTokenId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="21" parent="6" name="token">
<column id="22" parent="6" name="token">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="22" parent="6" name="dateCreated">
<column id="23" parent="6" name="dateCreated">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="23" parent="6" name="isDeleted">
<column id="24" parent="6" name="isDeleted">
<Position>4</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="24" parent="6" name="hash">
<column id="25" parent="6" name="hash">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="25" parent="6" name="sqlite_autoindex_api_tokens_1">
<index id="26" parent="6" name="sqlite_autoindex_api_tokens_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>apiTokenId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="26" parent="6">
<key id="27" parent="6">
<ColNames>apiTokenId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_api_tokens_1</UnderlyingIndexName>
</key>
<column id="27" parent="7" name="branchId">
<column id="28" parent="7" name="branchId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="28" parent="7" name="noteId">
<column id="29" parent="7" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="29" parent="7" name="parentNoteId">
<column id="30" parent="7" name="parentNoteId">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="30" parent="7" name="notePosition">
<column id="31" parent="7" name="notePosition">
<Position>4</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="31" parent="7" name="prefix">
<column id="32" parent="7" name="prefix">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="32" parent="7" name="isExpanded">
<column id="33" parent="7" name="isExpanded">
<Position>6</Position>
<DataType>BOOLEAN|0s</DataType>
</column>
<column id="33" parent="7" name="isDeleted">
<column id="34" parent="7" name="isDeleted">
<Position>7</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="34" parent="7" name="dateModified">
<column id="35" parent="7" name="dateModified">
<Position>8</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="35" parent="7" name="hash">
<column id="36" parent="7" name="hash">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="36" parent="7" name="dateCreated">
<column id="37" parent="7" name="dateCreated">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;1970-01-01T00:00:00.000Z&apos;</DefaultExpression>
</column>
<index id="37" parent="7" name="sqlite_autoindex_branches_1">
<index id="38" parent="7" name="sqlite_autoindex_branches_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>branchId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="38" parent="7" name="IDX_branches_noteId_parentNoteId">
<index id="39" parent="7" name="IDX_branches_noteId_parentNoteId">
<ColNames>noteId
parentNoteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="39" parent="7" name="IDX_branches_noteId">
<index id="40" parent="7" name="IDX_branches_noteId">
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="40" parent="7" name="IDX_branches_parentNoteId">
<index id="41" parent="7" name="IDX_branches_parentNoteId">
<ColNames>parentNoteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="41" parent="7">
<key id="42" parent="7">
<ColNames>branchId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName>
</key>
<column id="42" parent="8" name="eventId">
<column id="43" parent="8" name="eventId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="43" parent="8" name="noteId">
<column id="44" parent="8" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="44" parent="8" name="comment">
<column id="45" parent="8" name="comment">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="45" parent="8" name="dateCreated">
<column id="46" parent="8" name="dateCreated">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="46" parent="8" name="sqlite_autoindex_event_log_1">
<index id="47" parent="8" name="sqlite_autoindex_event_log_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>eventId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="47" parent="8">
<key id="48" parent="8">
<ColNames>eventId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_event_log_1</UnderlyingIndexName>
</key>
<column id="48" parent="9" name="imageId">
<column id="49" parent="9" name="imageId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="49" parent="9" name="format">
<column id="50" parent="9" name="format">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="50" parent="9" name="checksum">
<column id="51" parent="9" name="checksum">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="51" parent="9" name="name">
<column id="52" parent="9" name="name">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="52" parent="9" name="data">
<column id="53" parent="9" name="data">
<Position>5</Position>
<DataType>BLOB|0s</DataType>
</column>
<column id="53" parent="9" name="isDeleted">
<column id="54" parent="9" name="isDeleted">
<Position>6</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="54" parent="9" name="dateModified">
<column id="55" parent="9" name="dateModified">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="55" parent="9" name="dateCreated">
<column id="56" parent="9" name="dateCreated">
<Position>8</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="56" parent="9" name="hash">
<column id="57" parent="9" name="hash">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="57" parent="9" name="sqlite_autoindex_images_1">
<index id="58" parent="9" name="sqlite_autoindex_images_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>imageId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="58" parent="9">
<key id="59" parent="9">
<ColNames>imageId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName>
</key>
<column id="59" parent="10" name="labelId">
<column id="60" parent="10" name="labelId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="60" parent="10" name="noteId">
<column id="61" parent="10" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="61" parent="10" name="name">
<column id="62" parent="10" name="name">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="62" parent="10" name="value">
<column id="63" parent="10" name="value">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<column id="63" parent="10" name="position">
<column id="64" parent="10" name="position">
<Position>5</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="64" parent="10" name="dateCreated">
<column id="65" parent="10" name="dateCreated">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="65" parent="10" name="dateModified">
<column id="66" parent="10" name="dateModified">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="66" parent="10" name="isDeleted">
<column id="67" parent="10" name="isDeleted">
<Position>8</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="67" parent="10" name="hash">
<column id="68" parent="10" name="hash">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="68" parent="10" name="sqlite_autoindex_labels_1">
<index id="69" parent="10" name="sqlite_autoindex_labels_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>labelId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="69" parent="10" name="IDX_labels_noteId">
<index id="70" parent="10" name="IDX_labels_noteId">
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="70" parent="10" name="IDX_labels_name_value">
<index id="71" parent="10" name="IDX_labels_name_value">
<ColNames>name
value</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="71" parent="10">
<key id="72" parent="10">
<ColNames>labelId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName>
</key>
<column id="72" parent="11" name="noteImageId">
<column id="73" parent="11" name="noteImageId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="73" parent="11" name="noteId">
<column id="74" parent="11" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="74" parent="11" name="imageId">
<column id="75" parent="11" name="imageId">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="75" parent="11" name="isDeleted">
<column id="76" parent="11" name="isDeleted">
<Position>4</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="76" parent="11" name="dateModified">
<column id="77" parent="11" name="dateModified">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="77" parent="11" name="dateCreated">
<column id="78" parent="11" name="dateCreated">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="78" parent="11" name="hash">
<column id="79" parent="11" name="hash">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="79" parent="11" name="sqlite_autoindex_note_images_1">
<index id="80" parent="11" name="sqlite_autoindex_note_images_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteImageId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="80" parent="11" name="IDX_note_images_noteId_imageId">
<index id="81" parent="11" name="IDX_note_images_noteId_imageId">
<ColNames>noteId
imageId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="81" parent="11" name="IDX_note_images_noteId">
<index id="82" parent="11" name="IDX_note_images_noteId">
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="82" parent="11" name="IDX_note_images_imageId">
<index id="83" parent="11" name="IDX_note_images_imageId">
<ColNames>imageId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="83" parent="11">
<key id="84" parent="11">
<ColNames>noteImageId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_note_images_1</UnderlyingIndexName>
</key>
<column id="84" parent="12" name="noteRevisionId">
<column id="85" parent="12" name="noteRevisionId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="85" parent="12" name="noteId">
<column id="86" parent="12" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="86" parent="12" name="title">
<column id="87" parent="12" name="title">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="87" parent="12" name="content">
<column id="88" parent="12" name="content">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="88" parent="12" name="isProtected">
<column id="89" parent="12" name="isProtected">
<Position>5</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="89" parent="12" name="dateModifiedFrom">
<column id="90" parent="12" name="dateModifiedFrom">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="90" parent="12" name="dateModifiedTo">
<column id="91" parent="12" name="dateModifiedTo">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="91" parent="12" name="type">
<column id="92" parent="12" name="type">
<Position>8</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<column id="92" parent="12" name="mime">
<column id="93" parent="12" name="mime">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<column id="93" parent="12" name="hash">
<column id="94" parent="12" name="hash">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="94" parent="12" name="sqlite_autoindex_note_revisions_1">
<index id="95" parent="12" name="sqlite_autoindex_note_revisions_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteRevisionId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="95" parent="12" name="IDX_note_revisions_noteId">
<index id="96" parent="12" name="IDX_note_revisions_noteId">
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="96" parent="12" name="IDX_note_revisions_dateModifiedFrom">
<index id="97" parent="12" name="IDX_note_revisions_dateModifiedFrom">
<ColNames>dateModifiedFrom</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="97" parent="12" name="IDX_note_revisions_dateModifiedTo">
<index id="98" parent="12" name="IDX_note_revisions_dateModifiedTo">
<ColNames>dateModifiedTo</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="98" parent="12">
<key id="99" parent="12">
<ColNames>noteRevisionId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName>
</key>
<column id="99" parent="13" name="noteId">
<column id="100" parent="13" name="noteId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="100" parent="13" name="title">
<column id="101" parent="13" name="title">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;unnamed&quot;</DefaultExpression>
</column>
<column id="101" parent="13" name="content">
<column id="102" parent="13" name="content">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="102" parent="13" name="isProtected">
<column id="103" parent="13" name="isProtected">
<Position>4</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="103" parent="13" name="isDeleted">
<column id="104" parent="13" name="isDeleted">
<Position>5</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="104" parent="13" name="dateCreated">
<column id="105" parent="13" name="dateCreated">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="105" parent="13" name="dateModified">
<column id="106" parent="13" name="dateModified">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="106" parent="13" name="type">
<column id="107" parent="13" name="type">
<Position>8</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;text&apos;</DefaultExpression>
</column>
<column id="107" parent="13" name="mime">
<column id="108" parent="13" name="mime">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;text/html&apos;</DefaultExpression>
</column>
<column id="108" parent="13" name="hash">
<column id="109" parent="13" name="hash">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="109" parent="13" name="sqlite_autoindex_notes_1">
<index id="110" parent="13" name="sqlite_autoindex_notes_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="110" parent="13" name="IDX_notes_type">
<index id="111" parent="13" name="IDX_notes_type">
<ColNames>type</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="111" parent="13">
<key id="112" parent="13">
<ColNames>noteId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName>
</key>
<column id="112" parent="14" name="optionId">
<column id="113" parent="14" name="name">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="113" parent="14" name="name">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="114" parent="14" name="value">
<Position>3</Position>
<Position>2</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="115" parent="14" name="dateModified">
<Position>4</Position>
<Position>3</Position>
<DataType>INT|0s</DataType>
</column>
<column id="116" parent="14" name="isSynced">
<Position>5</Position>
<Position>4</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="117" parent="14" name="hash">
<Position>6</Position>
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="118" parent="14" name="dateCreated">
<Position>7</Position>
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;1970-01-01T00:00:00.000Z&apos;</DefaultExpression>
</column>
<index id="119" parent="14" name="sqlite_autoindex_options_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>optionId</ColNames>
<ColNames>name</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="120" parent="14">
<ColNames>optionId</ColNames>
<ColNames>name</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName>
</key>
@ -587,90 +583,156 @@ imageId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName>
</key>
<column id="128" parent="16" name="sourceId">
<column id="128" parent="16" name="relationId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="129" parent="16" name="dateCreated">
<column id="129" parent="16" name="sourceNoteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="130" parent="16" name="sqlite_autoindex_source_ids_1">
<column id="130" parent="16" name="name">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="131" parent="16" name="targetNoteId">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="132" parent="16" name="position">
<Position>5</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="133" parent="16" name="dateCreated">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="134" parent="16" name="dateModified">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="135" parent="16" name="isDeleted">
<Position>8</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="136" parent="16" name="hash">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="137" parent="16" name="sqlite_autoindex_relations_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>relationId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="138" parent="16" name="IDX_relation_sourceNoteId">
<ColNames>sourceNoteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="139" parent="16" name="IDX_relation_targetNoteId">
<ColNames>targetNoteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="140" parent="16">
<ColNames>relationId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_relations_1</UnderlyingIndexName>
</key>
<column id="141" parent="17" name="sourceId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="142" parent="17" name="dateCreated">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="143" parent="17" name="sqlite_autoindex_source_ids_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>sourceId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="131" parent="16">
<key id="144" parent="17">
<ColNames>sourceId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName>
</key>
<column id="132" parent="17" name="type">
<column id="145" parent="18" name="type">
<Position>1</Position>
<DataType>text|0s</DataType>
</column>
<column id="133" parent="17" name="name">
<column id="146" parent="18" name="name">
<Position>2</Position>
<DataType>text|0s</DataType>
</column>
<column id="134" parent="17" name="tbl_name">
<column id="147" parent="18" name="tbl_name">
<Position>3</Position>
<DataType>text|0s</DataType>
</column>
<column id="135" parent="17" name="rootpage">
<column id="148" parent="18" name="rootpage">
<Position>4</Position>
<DataType>integer|0s</DataType>
</column>
<column id="136" parent="17" name="sql">
<column id="149" parent="18" name="sql">
<Position>5</Position>
<DataType>text|0s</DataType>
</column>
<column id="137" parent="18" name="name">
<column id="150" parent="19" name="name">
<Position>1</Position>
</column>
<column id="138" parent="18" name="seq">
<column id="151" parent="19" name="seq">
<Position>2</Position>
</column>
<column id="139" parent="19" name="id">
<column id="152" parent="20" name="id">
<Position>1</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="140" parent="19" name="entityName">
<column id="153" parent="20" name="entityName">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="141" parent="19" name="entityId">
<column id="154" parent="20" name="entityId">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="142" parent="19" name="sourceId">
<column id="155" parent="20" name="sourceId">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="143" parent="19" name="syncDate">
<column id="156" parent="20" name="syncDate">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="144" parent="19" name="IDX_sync_entityName_entityId">
<index id="157" parent="20" name="IDX_sync_entityName_entityId">
<ColNames>entityName
entityId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="145" parent="19" name="IDX_sync_syncDate">
<index id="158" parent="20" name="IDX_sync_syncDate">
<ColNames>syncDate</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="146" parent="19">
<key id="159" parent="20">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>

View File

@ -0,0 +1,15 @@
CREATE TABLE relations
(
relationId TEXT not null primary key,
sourceNoteId TEXT not null,
name TEXT not null,
targetNoteId TEXT not null,
position INT default 0 not null,
dateCreated TEXT not null,
dateModified TEXT not null,
isDeleted INT not null
, hash TEXT DEFAULT "" NOT NULL);
CREATE INDEX IDX_relation_sourceNoteId
on relations (sourceNoteId);
CREATE INDEX IDX_relation_targetNoteId
on relations (targetNoteId);

View File

@ -4,6 +4,7 @@ const Image = require('../entities/image');
const NoteImage = require('../entities/note_image');
const Branch = require('../entities/branch');
const Label = require('../entities/label');
const Relation = require('../entities/relation');
const RecentNote = require('../entities/recent_note');
const ApiToken = require('../entities/api_token');
const Option = require('../entities/option');
@ -15,6 +16,9 @@ function createEntityFromRow(row) {
if (row.labelId) {
entity = new Label(row);
}
else if (row.relationId) {
entity = new Relation(row);
}
else if (row.noteRevisionId) {
entity = new NoteRevision(row);
}

40
src/entities/relation.js Normal file
View File

@ -0,0 +1,40 @@
"use strict";
const Entity = require('./entity');
const repository = require('../services/repository');
const dateUtils = require('../services/date_utils');
const sql = require('../services/sql');
class Relation extends Entity {
static get tableName() { return "relations"; }
static get primaryKeyName() { return "relationId"; }
static get hashedProperties() { return ["relationId", "sourceNoteId", "name", "targetNoteId", "dateModified", "dateCreated"]; }
async getSourceNote() {
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.sourceNoteId]);
}
async getTargetNote() {
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.targetNoteId]);
}
async beforeSaving() {
super.beforeSaving();
if (this.position === undefined) {
this.position = 1 + await sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM relations WHERE sourceNoteId = ?`, [this.sourceNoteId]);
}
if (!this.isDeleted) {
this.isDeleted = false;
}
if (!this.dateCreated) {
this.dateCreated = dateUtils.nowDate();
}
this.dateModified = dateUtils.nowDate();
}
}
module.exports = Relation;

View File

@ -0,0 +1,222 @@
import noteDetailService from '../services/note_detail.js';
import server from '../services/server.js';
import infoService from "../services/info.js";
const $dialog = $("#relations-dialog");
const $saveRelationsButton = $("#save-relations-button");
const $relationsBody = $('#relations-table tbody');
const relationsModel = new RelationsModel();
let relationNames = [];
function RelationsModel() {
const self = this;
this.relations = ko.observableArray();
this.updateRelationPositions = function() {
let position = 0;
// we need to update positions by searching in the DOM, because order of the
// relations in the viewmodel (self.relations()) stays the same
$relationsBody.find('input[name="position"]').each(function() {
const relation = self.getTargetRelation(this);
relation().position = position++;
});
};
this.loadRelations = async function() {
const noteId = noteDetailService.getCurrentNoteId();
const relations = await server.get('notes/' + noteId + '/relations');
self.relations(relations.map(ko.observable));
addLastEmptyRow();
relationNames = await server.get('relations/names');
// relation might not be rendered immediatelly so could not focus
setTimeout(() => $(".relation-name:last").focus(), 100);
$relationsBody.sortable({
handle: '.handle',
containment: $relationsBody,
update: this.updateRelationPositions
});
};
this.deleteRelation = function(data, event) {
const relation = self.getTargetRelation(event.target);
const relationData = relation();
if (relationData) {
relationData.isDeleted = 1;
relation(relationData);
addLastEmptyRow();
}
};
function isValid() {
for (let relations = self.relations(), i = 0; i < relations.length; i++) {
if (self.isEmptyName(i)) {
return false;
}
}
return true;
}
this.save = async function() {
// we need to defocus from input (in case of enter-triggered save) because value is updated
// on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
// stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
$saveRelationsButton.focus();
if (!isValid()) {
alert("Please fix all validation errors and try saving again.");
return;
}
self.updateRelationPositions();
const noteId = noteDetailService.getCurrentNoteId();
const relationsToSave = self.relations()
.map(relation => relation())
.filter(relation => relation.relationId !== "" || relation.name !== "");
const relations = await server.put('notes/' + noteId + '/relations', relationsToSave);
self.relations(relations.map(ko.observable));
addLastEmptyRow();
infoService.showMessage("Relations have been saved.");
noteDetailService.loadRelationList();
};
function addLastEmptyRow() {
const relations = self.relations().filter(attr => attr().isDeleted === 0);
const last = relations.length === 0 ? null : relations[relations.length - 1]();
if (!last || last.name.trim() !== "" || last.value !== "") {
self.relations.push(ko.observable({
relationId: '',
name: '',
value: '',
isDeleted: 0,
position: 0
}));
}
}
this.relationChanged = function (data, event) {
addLastEmptyRow();
const relation = self.getTargetRelation(event.target);
relation.valueHasMutated();
};
this.isNotUnique = function(index) {
const cur = self.relations()[index]();
if (cur.name.trim() === "") {
return false;
}
for (let relations = self.relations(), i = 0; i < relations.length; i++) {
const relation = relations[i]();
if (index !== i && cur.name === relation.name) {
return true;
}
}
return false;
};
this.isEmptyName = function(index) {
const cur = self.relations()[index]();
return cur.name.trim() === "" && (cur.relationId !== "" || cur.value !== "");
};
this.getTargetRelation = function(target) {
const context = ko.contextFor(target);
const index = context.$index();
return self.relations()[index];
}
}
async function showDialog() {
glob.activeDialog = $dialog;
await relationsModel.loadRelations();
$dialog.dialog({
modal: true,
width: 800,
height: 500
});
}
ko.applyBindings(relationsModel, document.getElementById('relations-dialog'));
$(document).on('focus', '.relation-name', function (e) {
if (!$(this).hasClass("ui-autocomplete-input")) {
$(this).autocomplete({
// shouldn't be required and autocomplete should just accept array of strings, but that fails
// because we have overriden filter() function in autocomplete.js
source: relationNames.map(relation => {
return {
relation: relation,
value: relation
}
}),
minLength: 0
});
}
$(this).autocomplete("search", $(this).val());
});
$(document).on('focus', '.relation-value', async function (e) {
if (!$(this).hasClass("ui-autocomplete-input")) {
const relationName = $(this).parent().parent().find('.relation-name').val();
if (relationName.trim() === "") {
return;
}
const relationValues = await server.get('relations/values/' + encodeURIComponent(relationName));
if (relationValues.length === 0) {
return;
}
$(this).autocomplete({
// shouldn't be required and autocomplete should just accept array of strings, but that fails
// because we have overriden filter() function in autocomplete.js
source: relationValues.map(relation => {
return {
label: relation,
value: relation
}
}),
minLength: 0
});
}
$(this).autocomplete("search", $(this).val());
});
export default {
showDialog
};

View File

@ -12,6 +12,7 @@ import recentChangesDialog from "../dialogs/recent_changes.js";
import sqlConsoleDialog from "../dialogs/sql_console.js";
import searchNotesService from "./search_notes.js";
import labelsDialog from "../dialogs/labels.js";
import relationsDialog from "../dialogs/relations.js";
import protectedSessionService from "./protected_session.js";
function registerEntrypoints() {
@ -40,6 +41,9 @@ function registerEntrypoints() {
$(".show-labels-button").click(labelsDialog.showDialog);
utils.bindShortcut('alt+l', labelsDialog.showDialog);
$(".show-relations-button").click(relationsDialog.showDialog);
utils.bindShortcut('alt+r', relationsDialog.showDialog);
$("#options-button").click(optionsDialog.showDialog);
utils.bindShortcut('alt+o', sqlConsoleDialog.showDialog);

View File

@ -0,0 +1,63 @@
"use strict";
const sql = require('../../services/sql');
const relationService = require('../../services/relations');
const repository = require('../../services/repository');
const Relation = require('../../entities/relation');
async function getNoteRelations(req) {
const noteId = req.params.noteId;
return await repository.getEntities("SELECT * FROM relations WHERE isDeleted = 0 AND sourceNoteId = ? ORDER BY position, dateCreated", [noteId]);
}
async function updateNoteRelations(req) {
const noteId = req.params.noteId;
const relations = req.body;
for (const relation of relations) {
let relationEntity;
if (relation.labelId) {
relationEntity = await repository.getRelation(relation.relationId);
}
else {
// if it was "created" and then immediatelly deleted, we just don't create it at all
if (relation.isDeleted) {
continue;
}
relationEntity = new Relation();
relationEntity.sourceNoteId = noteId;
}
relationEntity.name = relation.name;
relationEntity.targetNoteId = relation.targetNoteId;
relationEntity.position = relation.position;
relationEntity.isDeleted = relation.isDeleted;
await relationEntity.save();
}
return await repository.getEntities("SELECT * FROM relations WHERE isDeleted = 0 AND sourceNoteId = ? ORDER BY position, dateCreated", [noteId]);
}
async function getAllRelationNames() {
const names = await sql.getColumn("SELECT DISTINCT name FROM relations WHERE isDeleted = 0");
for (const relationName of relationService.BUILTIN_RELATIONS) {
if (!names.includes(relationName)) {
names.push(relationName);
}
}
names.sort();
return names;
}
module.exports = {
getNoteRelations,
updateNoteRelations,
getAllRelationNames
};

View File

@ -26,6 +26,7 @@ const anonymizationRoute = require('./api/anonymization');
const cleanupRoute = require('./api/cleanup');
const imageRoute = require('./api/image');
const labelsRoute = require('./api/labels');
const relationsRoute = require('./api/relations');
const scriptRoute = require('./api/script');
const senderRoute = require('./api/sender');
const filesRoute = require('./api/file_upload');
@ -137,6 +138,10 @@ function register(app) {
apiRoute(GET, '/api/labels/names', labelsRoute.getAllLabelNames);
apiRoute(GET, '/api/labels/values/:labelName', labelsRoute.getValuesForLabel);
apiRoute(GET, '/api/notes/:noteId/relations', relationsRoute.getNoteRelations);
apiRoute(PUT, '/api/notes/:noteId/relations', relationsRoute.updateNoteRelations);
apiRoute(GET, '/api/relations/names', relationsRoute.getAllRelationNames);
route(GET, '/api/images/:imageId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImage);
route(POST, '/api/images', [auth.checkApiAuthOrElectron, uploadMiddleware], imageRoute.uploadImage, apiResultHandler);

View File

@ -3,7 +3,7 @@
const build = require('./build');
const packageJson = require('../../package');
const APP_DB_VERSION = 105;
const APP_DB_VERSION = 106;
const SYNC_VERSION = 1;
module.exports = {

44
src/services/relations.js Normal file
View File

@ -0,0 +1,44 @@
"use strict";
const repository = require('./repository');
const Relation = require('../entities/relation');
const BUILTIN_RELATIONS = [
'exampleBuiltIn'
];
async function getNotesWithRelation(name, targetNoteId) {
let notes;
if (targetNoteId !== undefined) {
notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN relations ON notes.noteId = relations.sourceNoteId
WHERE notes.isDeleted = 0 AND relations.isDeleted = 0 AND relations.name = ? AND relations.targetNoteId = ?`, [name, targetNoteId]);
}
else {
notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN relations ON notes.noteId = relations.sourceNoteId
WHERE notes.isDeleted = 0 AND relations.isDeleted = 0 AND relations.name = ?`, [name]);
}
return notes;
}
async function getNoteWithRelation(name, value) {
const notes = await getNotesWithRelation(name, value);
return notes.length > 0 ? notes[0] : null;
}
async function createRelation(sourceNoteId, name, targetNoteId) {
return await new Relation({
sourceNoteId: sourceNoteId,
name: name,
targetNoteId: targetNoteId
}).save();
}
module.exports = {
getNotesWithRelation,
getNoteWithRelation,
createRelation,
BUILTIN_RELATIONS
};

View File

@ -41,6 +41,10 @@ async function getLabel(labelId) {
return await getEntity("SELECT * FROM labels WHERE labelId = ?", [labelId]);
}
async function getRelation(relationId) {
return await getEntity("SELECT * FROM relations WHERE relationId = ?", [relationId]);
}
async function getOption(name) {
return await getEntity("SELECT * FROM options WHERE name = ?", [name]);
}
@ -72,6 +76,7 @@ module.exports = {
getBranch,
getImage,
getLabel,
getRelation,
getOption,
updateEntity,
setEntityConstructor

View File

@ -236,6 +236,7 @@ const primaryKeys = {
"images": "imageId",
"note_images": "noteImageId",
"labels": "labelId",
"relations": "relationId",
"api_tokens": "apiTokenId",
"options": "name"
};

View File

@ -42,6 +42,10 @@ async function addLabelSync(labelId, sourceId) {
await addEntitySync("labels", labelId, sourceId);
}
async function addRelationSync(relationId, sourceId) {
await addEntitySync("relations", relationId, sourceId);
}
async function addApiTokenSync(apiTokenId, sourceId) {
await addEntitySync("api_tokens", apiTokenId, sourceId);
}
@ -101,6 +105,7 @@ async function fillAllSyncRows() {
await fillSyncRows("images", "imageId");
await fillSyncRows("note_images", "noteImageId");
await fillSyncRows("labels", "labelId");
await fillSyncRows("relations", "relationId");
await fillSyncRows("api_tokens", "apiTokenId");
await fillSyncRows("options", "name", 'isSynced = 1');
}
@ -115,6 +120,7 @@ module.exports = {
addImageSync,
addNoteImageSync,
addLabelSync,
addRelationSync,
addApiTokenSync,
addEntitySync,
cleanupSyncRowsForMissingEntities,

View File

@ -33,6 +33,9 @@ async function updateEntity(sync, entity, sourceId) {
else if (entityName === 'labels') {
await updateLabel(entity, sourceId);
}
else if (entityName === 'relations') {
await updateRelation(entity, sourceId);
}
else if (entityName === 'api_tokens') {
await updateApiToken(entity, sourceId);
}
@ -185,6 +188,20 @@ async function updateLabel(entity, sourceId) {
}
}
async function updateRelation(entity, sourceId) {
const origRelation = await sql.getRow("SELECT * FROM relations WHERE relationId = ?", [entity.relationId]);
if (!origRelation || origRelation.dateModified <= entity.dateModified) {
await sql.transactional(async () => {
await sql.replace("relations", entity);
await syncTableService.addRelationSync(entity.relationId, sourceId);
});
log.info("Update/sync relation " + entity.relationId);
}
}
async function updateApiToken(entity, sourceId) {
const apiTokenId = await sql.getRow("SELECT * FROM api_tokens WHERE apiTokenId = ?", [entity.apiTokenId]);

View File

@ -170,6 +170,7 @@
<ul class="dropdown-menu dropdown-menu-right">
<li><a id="show-note-revisions-button">Note revisions</a></li>
<li><a class="show-labels-button"><kbd>Alt+L</kbd> Labels</a></li>
<li><a class="show-relations-button"><kbd>Alt+R</kbd> Relations</a></li>
<li><a id="show-source-button">HTML source</a></li>
<li><a id="upload-file-button">Upload file</a></li>
</ul>
@ -587,6 +588,50 @@
</form>
</div>
<div id="relations-dialog" title="Note relations" style="display: none; padding: 20px;">
<form data-bind="submit: save">
<div style="text-align: center">
<button class="btn btn-large" style="width: 200px;" id="save-relations-button" type="submit">Save changes <kbd>enter</kbd></button>
</div>
<div style="height: 97%; overflow: auto">
<table id="relations-table" class="table">
<thead>
<tr>
<th></th>
<th>ID</th>
<th>Relation name</th>
<th>Target note</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: relations">
<tr data-bind="if: isDeleted == 0">
<td class="handle">
<span class="glyphicon glyphicon-resize-vertical"></span>
<input type="hidden" name="position" data-bind="value: position"/>
</td>
<!-- ID column has specific width because if it's empty its size can be deformed when dragging -->
<td data-bind="text: relationId" style="width: 150px;"></td>
<td>
<!-- Change to valueUpdate: blur is necessary because jQuery UI autocomplete hijacks change event -->
<input type="text" class="relation-name" data-bind="value: name, valueUpdate: 'blur', event: { blur: $parent.relationChanged }"/>
<div style="color: yellowgreen" data-bind="if: $parent.isNotUnique($index())"><span class="glyphicon glyphicon-info-sign"></span> Duplicate relation.</div>
<div style="color: red" data-bind="if: $parent.isEmptyName($index())">Relation name can't be empty.</div>
</td>
<td>
<input type="text" class="relation-value" data-bind="value: value, valueUpdate: 'blur', event: { blur: $parent.relationChanged }" style="width: 300px"/>
</td>
<td title="Delete" style="padding: 13px;">
<span class="glyphicon glyphicon-trash" data-bind="click: $parent.deleteRelation"></span>
</td>
</tr>
</tbody>
</table>
</div>
</form>
</div>
<div id="tooltip" style="display: none;"></div>
<script type="text/javascript">