Compare commits

...

16 Commits

Author SHA1 Message Date
zadam
37da0adb8a release 0.42.2 2020-05-12 16:46:45 +02:00
zadam
4f50864ec8 better UX when deleting notes - focus in note tree is moved to the next/previous note 2020-05-12 13:40:42 +02:00
zadam
30b9ef8604 fix tab title of deleted note 2020-05-12 12:45:32 +02:00
zadam
b063b4c528 read only view images should not overflow 2020-05-12 12:28:59 +02:00
zadam
e08b0141a4 when expanding/collpasing, set the flag also to the tree cache 2020-05-12 10:52:07 +02:00
zadam
9d8b8e26a1 attach extension to download file if note present 2020-05-12 10:28:31 +02:00
zadam
cb70109ee7 fix creating new promoted attributes, closes #1008
(cherry picked from commit 2e0fb8aaf1)
2020-05-12 00:00:59 +02:00
zadam
e541abbd60 disable hiding the body to not hide the noscript element 2020-05-11 22:44:10 +02:00
Naveen M V
940a70adc5 Fix regex bug (#1005)
(cherry picked from commit 9a662f76da)
2020-05-11 22:06:39 +02:00
zadam
88e8eb7e9c fixed context menu positioning when scaling is active 2020-05-11 20:08:55 +02:00
zadam
b365c186a1 fixed clicking on links in read only view 2020-05-11 19:38:14 +02:00
zadam
64c9734f05 transform setup.js to the webpacked version in the build 2020-05-08 20:50:53 +02:00
zadam
48c843c087 fix setup on server edition 2020-05-08 10:24:57 +02:00
zadam
0e4eec10b9 added "restore this revision" button 2020-05-07 23:34:13 +02:00
zadam
a3661cb763 fix display of buttons for revisions when there is none 2020-05-07 23:14:21 +02:00
zadam
115879ec4a fix note revisions displaying wrong tooltip 2020-05-07 23:02:46 +02:00
36 changed files with 249 additions and 824 deletions

View File

@@ -1,692 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<dataSource name="document.db">
<database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.18">
<root id="1">
<ServerVersion>3.16.1</ServerVersion>
</root>
<schema id="2" parent="1" name="main">
<Current>1</Current>
</schema>
<collation id="3" parent="1" name="BINARY"/>
<collation id="4" parent="1" name="NOCASE"/>
<collation id="5" parent="1" name="RTRIM"/>
<table id="6" parent="2" name="api_tokens"/>
<table id="7" parent="2" name="attributes"/>
<table id="8" parent="2" name="branches"/>
<table id="9" parent="2" name="note_contents"/>
<table id="10" parent="2" name="note_revision_contents"/>
<table id="11" parent="2" name="note_revisions"/>
<table id="12" parent="2" name="notes"/>
<table id="13" parent="2" name="options"/>
<table id="14" parent="2" name="recent_notes"/>
<table id="15" parent="2" name="source_ids"/>
<table id="16" parent="2" name="sqlite_master">
<System>1</System>
</table>
<table id="17" parent="2" name="sqlite_sequence">
<System>1</System>
</table>
<table id="18" parent="2" name="sync"/>
<column id="19" parent="6" name="apiTokenId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="20" parent="6" name="token">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="21" parent="6" name="utcDateCreated">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="22" parent="6" name="isDeleted">
<Position>4</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="23" parent="6" name="hash">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="24" parent="6" name="sqlite_autoindex_api_tokens_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>apiTokenId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="25" parent="6">
<ColNames>apiTokenId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_api_tokens_1</UnderlyingIndexName>
</key>
<column id="26" parent="7" name="attributeId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="27" parent="7" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="28" parent="7" name="type">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="29" parent="7" name="name">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="30" parent="7" name="value">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<column id="31" parent="7" name="position">
<Position>6</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="32" parent="7" name="utcDateCreated">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="33" parent="7" name="utcDateModified">
<Position>8</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="34" parent="7" name="isDeleted">
<Position>9</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="35" parent="7" name="deleteId">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<DefaultExpression>NULL</DefaultExpression>
</column>
<column id="36" parent="7" name="hash">
<Position>11</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="37" parent="7" name="isInheritable">
<Position>12</Position>
<DataType>int|0s</DataType>
<DefaultExpression>0</DefaultExpression>
</column>
<index id="38" parent="7" name="sqlite_autoindex_attributes_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>attributeId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="39" parent="7" name="IDX_attributes_noteId_index">
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="40" parent="7" name="IDX_attributes_name_value">
<ColNames>name
value</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="41" parent="7" name="IDX_attributes_value_index">
<ColNames>value</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="42" parent="7">
<ColNames>attributeId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_attributes_1</UnderlyingIndexName>
</key>
<column id="43" parent="8" name="branchId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="44" parent="8" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="45" parent="8" name="parentNoteId">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="46" parent="8" name="notePosition">
<Position>4</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="47" parent="8" name="prefix">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="48" parent="8" name="isExpanded">
<Position>6</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="49" parent="8" name="isDeleted">
<Position>7</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="50" parent="8" name="deleteId">
<Position>8</Position>
<DataType>TEXT|0s</DataType>
<DefaultExpression>NULL</DefaultExpression>
</column>
<column id="51" parent="8" name="utcDateModified">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="52" parent="8" name="utcDateCreated">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="53" parent="8" name="hash">
<Position>11</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="54" parent="8" name="sqlite_autoindex_branches_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>branchId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="55" parent="8" name="IDX_branches_noteId_parentNoteId">
<ColNames>noteId
parentNoteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="56" parent="8" name="IDX_branches_parentNoteId">
<ColNames>parentNoteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="57" parent="8">
<ColNames>branchId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName>
</key>
<column id="58" parent="9" name="noteId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="59" parent="9" name="content">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<DefaultExpression>NULL</DefaultExpression>
</column>
<column id="60" parent="9" name="hash">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="61" parent="9" name="utcDateModified">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="62" parent="9" name="sqlite_autoindex_note_contents_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="63" parent="9">
<ColNames>noteId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_note_contents_1</UnderlyingIndexName>
</key>
<column id="64" parent="10" name="noteRevisionId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="65" parent="10" name="content">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="66" parent="10" name="hash">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<column id="67" parent="10" name="utcDateModified">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="68" parent="10" name="sqlite_autoindex_note_revision_contents_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteRevisionId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="69" parent="10">
<ColNames>noteRevisionId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_note_revision_contents_1</UnderlyingIndexName>
</key>
<column id="70" parent="11" name="noteRevisionId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="71" parent="11" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="72" parent="11" name="title">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="73" parent="11" name="contentLength">
<Position>4</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="74" parent="11" name="isErased">
<Position>5</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="75" parent="11" name="isProtected">
<Position>6</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="76" parent="11" name="utcDateLastEdited">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="77" parent="11" name="utcDateCreated">
<Position>8</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="78" parent="11" name="utcDateModified">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="79" parent="11" name="dateLastEdited">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="80" parent="11" name="dateCreated">
<Position>11</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="81" parent="11" name="type">
<Position>12</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<column id="82" parent="11" name="mime">
<Position>13</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<column id="83" parent="11" name="hash">
<Position>14</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<index id="84" parent="11" name="sqlite_autoindex_note_revisions_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteRevisionId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="85" parent="11" name="IDX_note_revisions_noteId">
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="86" parent="11" name="IDX_note_revisions_utcDateLastEdited">
<ColNames>utcDateLastEdited</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="87" parent="11" name="IDX_note_revisions_utcDateCreated">
<ColNames>utcDateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="88" parent="11" name="IDX_note_revisions_dateLastEdited">
<ColNames>dateLastEdited</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="89" parent="11" name="IDX_note_revisions_dateCreated">
<ColNames>dateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="90" parent="11">
<ColNames>noteRevisionId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName>
</key>
<column id="91" parent="12" name="noteId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="92" parent="12" name="title">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;note&quot;</DefaultExpression>
</column>
<column id="93" parent="12" name="contentLength">
<Position>3</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="94" parent="12" name="isProtected">
<Position>4</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="95" parent="12" name="type">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;text&apos;</DefaultExpression>
</column>
<column id="96" parent="12" name="mime">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;text/html&apos;</DefaultExpression>
</column>
<column id="97" parent="12" name="hash">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="98" parent="12" name="isDeleted">
<Position>8</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="99" parent="12" name="deleteId">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<DefaultExpression>NULL</DefaultExpression>
</column>
<column id="100" parent="12" name="isErased">
<Position>10</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="101" parent="12" name="dateCreated">
<Position>11</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="102" parent="12" name="dateModified">
<Position>12</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="103" parent="12" name="utcDateCreated">
<Position>13</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="104" parent="12" name="utcDateModified">
<Position>14</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="105" parent="12" name="sqlite_autoindex_notes_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="106" parent="12" name="IDX_notes_title">
<ColNames>title</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="107" parent="12" name="IDX_notes_type">
<ColNames>type</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="108" parent="12" name="IDX_notes_isDeleted">
<ColNames>isDeleted</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="109" parent="12" name="IDX_notes_dateCreated">
<ColNames>dateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="110" parent="12" name="IDX_notes_dateModified">
<ColNames>dateModified</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="111" parent="12" name="IDX_notes_utcDateCreated">
<ColNames>utcDateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="112" parent="12" name="IDX_notes_utcDateModified">
<ColNames>utcDateModified</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="113" parent="12">
<ColNames>noteId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName>
</key>
<column id="114" parent="13" name="name">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="115" parent="13" name="value">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="116" parent="13" name="isSynced">
<Position>3</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="117" parent="13" name="hash">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="118" parent="13" name="utcDateCreated">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="119" parent="13" name="utcDateModified">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="120" parent="13" name="sqlite_autoindex_options_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>name</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="121" parent="13">
<ColNames>name</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName>
</key>
<column id="122" parent="14" name="noteId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="123" parent="14" name="notePath">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="124" parent="14" name="hash">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="125" parent="14" name="utcDateCreated">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="126" parent="14" name="isDeleted">
<Position>5</Position>
<DataType>INT|0s</DataType>
</column>
<index id="127" parent="14" name="sqlite_autoindex_recent_notes_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="128" parent="14">
<ColNames>noteId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName>
</key>
<column id="129" parent="15" name="sourceId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="130" parent="15" name="utcDateCreated">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="131" parent="15" name="sqlite_autoindex_source_ids_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>sourceId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="132" parent="15" name="IDX_source_ids_utcDateCreated">
<ColNames>utcDateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="133" parent="15">
<ColNames>sourceId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName>
</key>
<column id="134" parent="16" name="type">
<Position>1</Position>
<DataType>text|0s</DataType>
</column>
<column id="135" parent="16" name="name">
<Position>2</Position>
<DataType>text|0s</DataType>
</column>
<column id="136" parent="16" name="tbl_name">
<Position>3</Position>
<DataType>text|0s</DataType>
</column>
<column id="137" parent="16" name="rootpage">
<Position>4</Position>
<DataType>integer|0s</DataType>
</column>
<column id="138" parent="16" name="sql">
<Position>5</Position>
<DataType>text|0s</DataType>
</column>
<column id="139" parent="17" name="name">
<Position>1</Position>
</column>
<column id="140" parent="17" name="seq">
<Position>2</Position>
</column>
<column id="141" parent="18" name="id">
<Position>1</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="142" parent="18" name="entityName">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="143" parent="18" name="entityId">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="144" parent="18" name="sourceId">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="145" parent="18" name="isSynced">
<Position>5</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="146" parent="18" name="utcSyncDate">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="147" parent="18" name="IDX_sync_entityName_entityId">
<ColNames>entityName
entityId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="148" parent="18" name="IDX_sync_utcSyncDate">
<ColNames>utcSyncDate</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="149" parent="18">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
</database-model>
</dataSource>

View File

@@ -1,2 +0,0 @@
#n:main
!<md> [0, 0, null, null, -2147483648, -2147483648]

View File

@@ -1,4 +1,4 @@
FROM node:12.16.2-alpine FROM node:12.16.3-alpine
# Create app directory # Create app directory
WORKDIR /usr/src/app WORKDIR /usr/src/app

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
PKG_DIR=dist/trilium-linux-x64-server PKG_DIR=dist/trilium-linux-x64-server
NODE_VERSION=12.16.2 NODE_VERSION=12.16.3
if [ "$1" != "DONTCOPY" ] if [ "$1" != "DONTCOPY" ]
then then

View File

@@ -34,4 +34,5 @@ find $DIR/libraries -name "*.map" -type f -delete
rm -r $DIR/src/public/app rm -r $DIR/src/public/app
sed -i -e 's/app\/desktop.js/app-dist\/desktop.js/g' $DIR/src/views/desktop.ejs sed -i -e 's/app\/desktop.js/app-dist\/desktop.js/g' $DIR/src/views/desktop.ejs
sed -i -e 's/app\/mobile.js/app-dist\/mobile.js/g' $DIR/src/views/mobile.ejs sed -i -e 's/app\/mobile.js/app-dist\/mobile.js/g' $DIR/src/views/mobile.ejs
sed -i -e 's/app\/setup.js/app-dist\/setup.js/g' $DIR/src/views/setup.ejs

8
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "trilium", "name": "trilium",
"version": "0.42.0-beta", "version": "0.42.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -3345,9 +3345,9 @@
} }
}, },
"electron": { "electron": {
"version": "9.0.0-beta.22", "version": "9.0.0-beta.24",
"resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0-beta.22.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0-beta.24.tgz",
"integrity": "sha512-dfqAf+CXXTKcNDj7DU7mYsmx+oZQcXOvJnZ8ZsgAHjrE9Tv8zsYUgCP3JlO4Z8CIazgleKXYmgh6H2stdK7fEA==", "integrity": "sha512-25L3XMqm/1CCaV5CgU5ZkhKXw9830WeipJrTW0+VC5XTKp/3xHwhxyQ5G1kQnOTJd7IGwOamvw237D6e1YKnng==",
"dev": true, "dev": true,
"requires": { "requires": {
"@electron/get": "^1.0.1", "@electron/get": "^1.0.1",

View File

@@ -2,7 +2,7 @@
"name": "trilium", "name": "trilium",
"productName": "Trilium Notes", "productName": "Trilium Notes",
"description": "Trilium Notes", "description": "Trilium Notes",
"version": "0.42.1", "version": "0.42.2",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"main": "electron.js", "main": "electron.js",
"bin": { "bin": {
@@ -78,7 +78,7 @@
"yazl": "^2.5.1" "yazl": "^2.5.1"
}, },
"devDependencies": { "devDependencies": {
"electron": "9.0.0-beta.22", "electron": "9.0.0-beta.24",
"electron-builder": "22.6.0", "electron-builder": "22.6.0",
"electron-packager": "14.2.1", "electron-packager": "14.2.1",
"electron-rebuild": "1.10.1", "electron-rebuild": "1.10.1",

View File

@@ -105,7 +105,6 @@ class Attribute extends Entity {
// cannot be static! // cannot be static!
updatePojo(pojo) { updatePojo(pojo) {
delete pojo.isOwned;
delete pojo.__note; delete pojo.__note;
} }
@@ -124,4 +123,4 @@ class Attribute extends Entity {
} }
} }
module.exports = Attribute; module.exports = Attribute;

View File

@@ -411,10 +411,6 @@ class Note extends Entity {
} }
}); });
for (const attr of filteredAttributes) {
attr.isOwned = attr.noteId === this.noteId;
}
this.__attributeCache = filteredAttributes; this.__attributeCache = filteredAttributes;
} }
@@ -946,4 +942,4 @@ class Note extends Entity {
} }
} }
module.exports = Note; module.exports = Note;

View File

@@ -8,6 +8,7 @@ import contextMenu from "./services/context_menu.js";
import DesktopMainWindowLayout from "./layouts/desktop_main_window_layout.js"; import DesktopMainWindowLayout from "./layouts/desktop_main_window_layout.js";
import glob from "./services/glob.js"; import glob from "./services/glob.js";
import DesktopExtraWindowLayout from "./layouts/desktop_extra_window_layout.js"; import DesktopExtraWindowLayout from "./layouts/desktop_extra_window_layout.js";
import zoomService from './services/zoom.js';
glob.setupGlobs(); glob.setupGlobs();
@@ -133,9 +134,11 @@ if (utils.isElectron()) {
return; return;
} }
const zoomLevel = zoomService.getCurrentZoom();
contextMenu.show({ contextMenu.show({
x: params.x, x: params.x / zoomLevel,
y: params.y, y: params.y / zoomLevel,
items, items,
selectMenuItemHandler: ({command, spellingSuggestion}) => { selectMenuItemHandler: ({command, spellingSuggestion}) => {
if (command === 'replaceMisspelling') { if (command === 'replaceMisspelling') {
@@ -144,4 +147,4 @@ if (utils.isElectron()) {
} }
}); });
}); });
} }

View File

@@ -59,8 +59,8 @@ function AttributesModel() {
}); });
}; };
async function showAttributes(attributes) { async function showAttributes(noteId, attributes) {
const ownedAttributes = attributes.filter(attr => attr.isOwned); const ownedAttributes = attributes.filter(attr => attr.noteId === noteId);
for (const attr of ownedAttributes) { for (const attr of ownedAttributes) {
attr.labelValue = attr.type === 'label' ? attr.value : ''; attr.labelValue = attr.type === 'label' ? attr.value : '';
@@ -86,7 +86,7 @@ function AttributesModel() {
addLastEmptyRow(); addLastEmptyRow();
const inheritedAttributes = attributes.filter(attr => !attr.isOwned); const inheritedAttributes = attributes.filter(attr => attr.noteId !== noteId);
self.inheritedAttributes(inheritedAttributes); self.inheritedAttributes(inheritedAttributes);
} }
@@ -96,7 +96,7 @@ function AttributesModel() {
const attributes = await server.get('notes/' + noteId + '/attributes'); const attributes = await server.get('notes/' + noteId + '/attributes');
await showAttributes(attributes); await showAttributes(noteId, attributes);
// attribute might not be rendered immediatelly so could not focus // attribute might not be rendered immediatelly so could not focus
setTimeout(() => $(".attribute-type-select:last").trigger('focus'), 1000); setTimeout(() => $(".attribute-type-select:last").trigger('focus'), 1000);
@@ -166,7 +166,7 @@ function AttributesModel() {
const attributes = await server.put('notes/' + noteId + '/attributes', attributesToSave); const attributes = await server.put('notes/' + noteId + '/attributes', attributesToSave);
await showAttributes(attributes); await showAttributes(noteId, attributes);
toastService.showMessage("Attributes have been saved."); toastService.showMessage("Attributes have been saved.");
}; };
@@ -311,4 +311,4 @@ $dialog.on('focus', '.label-value', function (e) {
$el: $(this), $el: $(this),
open: true open: true
}) })
}); });

View File

@@ -37,15 +37,18 @@ export async function showNoteRevisionsDialog(noteId, noteRevisionId) {
async function loadNoteRevisions(noteId, noteRevId) { async function loadNoteRevisions(noteId, noteRevId) {
$list.empty(); $list.empty();
$content.empty(); $content.empty();
$titleButtons.empty();
note = appContext.tabManager.getActiveTabNote(); note = appContext.tabManager.getActiveTabNote();
revisionItems = await server.get(`notes/${noteId}/revisions`); revisionItems = await server.get(`notes/${noteId}/revisions`);
for (const item of revisionItems) { for (const item of revisionItems) {
$list.append($('<a class="dropdown-item" tabindex="0">') $list.append(
.text(item.dateLastEdited.substr(0, 16) + ` (${item.contentLength} bytes)`) $('<a class="dropdown-item" tabindex="0">')
.attr('data-note-revision-id', item.noteRevisionId)) .text(item.dateLastEdited.substr(0, 16) + ` (${item.contentLength} bytes)`)
.attr('title', 'This revision was last edited on ' + item.dateLastEdited); .attr('data-note-revision-id', item.noteRevisionId)
.attr('title', 'This revision was last edited on ' + item.dateLastEdited)
);
} }
$listDropdown.dropdown('show'); $listDropdown.dropdown('show');
@@ -60,6 +63,8 @@ async function loadNoteRevisions(noteId, noteRevId) {
$title.text("No revisions for this note yet..."); $title.text("No revisions for this note yet...");
noteRevisionId = null; noteRevisionId = null;
} }
$eraseAllRevisionsButton.toggle(revisionItems.length > 0);
} }
$dialog.on('shown.bs.modal', () => { $dialog.on('shown.bs.modal', () => {
@@ -77,6 +82,21 @@ async function setContentPane() {
$title.html(revisionItem.title); $title.html(revisionItem.title);
const $restoreRevisionButton = $('<button class="btn btn-sm" type="button">Restore this revision</button>');
$restoreRevisionButton.on('click', async () => {
const confirmDialog = await import('../dialogs/confirm.js');
const text = 'Do you want to restore this revision? This will overwrite current title/content of the note with this revision.';
if (await confirmDialog.confirm(text)) {
await server.put(`notes/${revisionItem.noteId}/restore-revision/${revisionItem.noteRevisionId}`);
$dialog.modal('hide');
toastService.showMessage('Note revision has been restored.');
}
});
const $eraseRevisionButton = $('<button class="btn btn-sm" type="button">Delete this revision</button>'); const $eraseRevisionButton = $('<button class="btn btn-sm" type="button">Delete this revision</button>');
$eraseRevisionButton.on('click', async () => { $eraseRevisionButton.on('click', async () => {
@@ -93,6 +113,8 @@ async function setContentPane() {
}); });
$titleButtons $titleButtons
.append($restoreRevisionButton)
.append(' &nbsp; ')
.append($eraseRevisionButton) .append($eraseRevisionButton)
.append(' &nbsp; '); .append(' &nbsp; ');

View File

@@ -24,7 +24,6 @@ import NoteRevisionsWidget from "../widgets/collapsible_widgets/note_revisions.j
import SimilarNotesWidget from "../widgets/collapsible_widgets/similar_notes.js"; import SimilarNotesWidget from "../widgets/collapsible_widgets/similar_notes.js";
import WhatLinksHereWidget from "../widgets/collapsible_widgets/what_links_here.js"; import WhatLinksHereWidget from "../widgets/collapsible_widgets/what_links_here.js";
import SidePaneToggles from "../widgets/side_pane_toggles.js"; import SidePaneToggles from "../widgets/side_pane_toggles.js";
import appContext from "../services/app_context.js";
const RIGHT_PANE_CSS = ` const RIGHT_PANE_CSS = `
<style> <style>
@@ -117,6 +116,7 @@ export default class DesktopMainWindowLayout {
.hideInZenMode()) .hideInZenMode())
.child(new FlexContainer('row') .child(new FlexContainer('row')
.collapsible() .collapsible()
.filling()
.child(new SidePaneContainer('left') .child(new SidePaneContainer('left')
.hideInZenMode() .hideInZenMode()
.child(new GlobalButtonsWidget()) .child(new GlobalButtonsWidget())
@@ -153,4 +153,4 @@ export default class DesktopMainWindowLayout {
.child(new SidePaneToggles().hideInZenMode()) .child(new SidePaneToggles().hideInZenMode())
); );
} }
} }

View File

@@ -4,7 +4,7 @@ import DialogCommandExecutor from "./dialog_command_executor.js";
import Entrypoints from "./entrypoints.js"; import Entrypoints from "./entrypoints.js";
import options from "./options.js"; import options from "./options.js";
import utils from "./utils.js"; import utils from "./utils.js";
import ZoomService from "./zoom.js"; import zoomService from "./zoom.js";
import TabManager from "./tab_manager.js"; import TabManager from "./tab_manager.js";
import treeService from "./tree.js"; import treeService from "./tree.js";
import Component from "../widgets/component.js"; import Component from "../widgets/component.js";
@@ -73,7 +73,7 @@ class AppContext extends Component {
} }
if (utils.isElectron()) { if (utils.isElectron()) {
this.child(new ZoomService()); this.child(zoomService);
} }
this.triggerEvent('initialRenderComplete'); this.triggerEvent('initialRenderComplete');
@@ -134,4 +134,4 @@ $(window).on('hashchange', function() {
} }
}); });
export default appContext; export default appContext;

View File

@@ -81,24 +81,29 @@ function goToLink(e) {
} }
else if (e.which === 1) { else if (e.which === 1) {
const activeTabContext = appContext.tabManager.getActiveTabContext(); const activeTabContext = appContext.tabManager.getActiveTabContext();
activeTabContext.setNote(notePath) activeTabContext.setNote(notePath);
} }
else { else {
return false; return false;
} }
} }
else { else {
const address = $link.attr('href'); if (e.which === 1) {
const address = $link.attr('href');
if (address && address.startsWith('http')) { if (address && address.startsWith('http')) {
window.open(address, '_blank'); window.open(address, '_blank');
}
}
else {
return false;
} }
} }
return true; return true;
} }
function newTabContextMenu(e) { function linkContextMenu(e) {
const $link = $(e.target).closest("a"); const $link = $(e.target).closest("a");
const notePath = getNotePathFromLink($link); const notePath = getNotePathFromLink($link);
@@ -113,7 +118,7 @@ function newTabContextMenu(e) {
x: e.pageX, x: e.pageX,
y: e.pageY, y: e.pageY,
items: [ items: [
{title: "Open note in new tab", command: "openNoteInNewTab", uiIcon: "arrow-up-right"}, {title: "Open note in new tab", command: "openNoteInNewTab", uiIcon: "empty"},
{title: "Open note in new window", command: "openNoteInNewWindow", uiIcon: "window-open"} {title: "Open note in new window", command: "openNoteInNewWindow", uiIcon: "window-open"}
], ],
selectMenuItemHandler: ({command}) => { selectMenuItemHandler: ({command}) => {
@@ -155,21 +160,23 @@ $(document).on('mousedown', '.note-detail-text a', function (e) {
$(document).on('mousedown', '.note-detail-book a', goToLink); $(document).on('mousedown', '.note-detail-book a', goToLink);
$(document).on('mousedown', '.note-detail-render a', goToLink); $(document).on('mousedown', '.note-detail-render a', goToLink);
$(document).on('mousedown', '.note-detail-text.ck-read-only a,.note-detail-text a.reference-link', goToLink); $(document).on('mousedown', '.note-detail-text a.reference-link', goToLink);
$(document).on('mousedown', '.note-detail-readonly-text a', goToLink);
$(document).on('mousedown', 'a.ck-link-actions__preview', goToLink); $(document).on('mousedown', 'a.ck-link-actions__preview', goToLink);
$(document).on('click', 'a.ck-link-actions__preview', e => { $(document).on('click', 'a.ck-link-actions__preview', e => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
}); });
$(document).on('contextmenu', 'a.ck-link-actions__preview', newTabContextMenu); $(document).on('contextmenu', 'a.ck-link-actions__preview', linkContextMenu);
$(document).on('contextmenu', '.note-detail-text a', newTabContextMenu); $(document).on('contextmenu', '.note-detail-text a', linkContextMenu);
$(document).on('contextmenu', "a[data-action='note']", newTabContextMenu); $(document).on('contextmenu', '.note-detail-readonly-text a', linkContextMenu);
$(document).on('contextmenu', ".note-detail-render a", newTabContextMenu); $(document).on('contextmenu', "a[data-action='note']", linkContextMenu);
$(document).on('contextmenu', ".note-paths-widget a", newTabContextMenu); $(document).on('contextmenu', ".note-detail-render a", linkContextMenu);
$(document).on('contextmenu', ".note-paths-widget a", linkContextMenu);
export default { export default {
getNotePathFromUrl, getNotePathFromUrl,
createNoteLink, createNoteLink,
goToLink goToLink
}; };

View File

@@ -37,6 +37,10 @@ function subscribeToMessages(messageHandler) {
// used to serialize sync operations // used to serialize sync operations
let consumeQueuePromise = null; let consumeQueuePromise = null;
// most sync events are sent twice - once immediatelly after finishing the transaction and once during the scheduled ping
// but we want to process only once
const receivedSyncIds = new Set();
async function handleMessage(event) { async function handleMessage(event) {
const message = JSON.parse(event.data); const message = JSON.parse(event.data);
@@ -52,14 +56,19 @@ async function handleMessage(event) {
if (syncRows.length > 0) { if (syncRows.length > 0) {
const filteredRows = syncRows.filter(row => const filteredRows = syncRows.filter(row =>
row.entityName !== 'recent_notes' !receivedSyncIds.has(row.id)
&& row.entityName !== 'recent_notes'
&& (row.entityName !== 'options' || row.entityId !== 'openTabs')); && (row.entityName !== 'options' || row.entityId !== 'openTabs'));
if (filteredRows.length > 0) { if (filteredRows.length > 0) {
console.debug(utils.now(), "Sync data: ", filteredRows); console.debug(utils.now(), "Sync data: ", filteredRows);
} }
syncDataQueue.push(...syncRows); for (const row of filteredRows) {
receivedSyncIds.add(row.id);
}
syncDataQueue.push(...filteredRows);
// we set lastAcceptedSyncId even before sync processing and send ping so that backend can start sending more updates // we set lastAcceptedSyncId even before sync processing and send ping so that backend can start sending more updates
lastAcceptedSyncId = Math.max(lastAcceptedSyncId, syncRows[syncRows.length - 1].id); lastAcceptedSyncId = Math.max(lastAcceptedSyncId, syncRows[syncRows.length - 1].id);
@@ -170,7 +179,7 @@ function connectWebSocket() {
async function sendPing() { async function sendPing() {
if (Date.now() - lastPingTs > 30000) { if (Date.now() - lastPingTs > 30000) {
console.log(utils.now(), "Lost websocket connection to the backend"); console.log(utils.now(), "Lost websocket connection to the backend. If you keep having this issue repeatedly, you might want to check your reverse proxy (nginx, apache) configuration and allow/unblock WebSocket.");
} }
if (ws.readyState === ws.OPEN) { if (ws.readyState === ws.OPEN) {
@@ -374,4 +383,4 @@ export default {
subscribeToMessages, subscribeToMessages,
waitForSyncId, waitForSyncId,
waitForMaxKnownSyncId waitForMaxKnownSyncId
}; };

View File

@@ -5,31 +5,33 @@ import utils from "../services/utils.js";
const MIN_ZOOM = 0.5; const MIN_ZOOM = 0.5;
const MAX_ZOOM = 2.0; const MAX_ZOOM = 2.0;
export default class ZoomService extends Component { class ZoomService extends Component {
constructor() { constructor() {
super(); super();
this.setZoomFactor(options.getFloat('zoomFactor')); options.initializedPromise.then(() => {
this.setZoomFactor(options.getFloat('zoomFactor'));
});
} }
setZoomFactor(zoomFactor) { setZoomFactor(zoomFactor) {
zoomFactor = parseFloat(zoomFactor); zoomFactor = parseFloat(zoomFactor);
const webFrame = utils.dynamicRequire('electron').webFrame; const webFrame = utils.dynamicRequire('electron').webFrame;
webFrame.setZoomFactor(zoomFactor); webFrame.setZoomFactor(zoomFactor);
} }
async setZoomFactorAndSave(zoomFactor) { async setZoomFactorAndSave(zoomFactor) {
if (zoomFactor >= MIN_ZOOM && zoomFactor <= MAX_ZOOM) { if (zoomFactor >= MIN_ZOOM && zoomFactor <= MAX_ZOOM) {
this.setZoomFactor(zoomFactor); this.setZoomFactor(zoomFactor);
await options.save('zoomFactor', zoomFactor); await options.save('zoomFactor', zoomFactor);
} }
else { else {
console.log(`Zoom factor ${zoomFactor} outside of the range, ignored.`); console.log(`Zoom factor ${zoomFactor} outside of the range, ignored.`);
} }
} }
getCurrentZoom() { getCurrentZoom() {
return utils.dynamicRequire('electron').webFrame.getZoomFactor(); return utils.dynamicRequire('electron').webFrame.getZoomFactor();
} }
@@ -45,4 +47,8 @@ export default class ZoomService extends Component {
setZoomFactorAndSaveEvent({zoomFactor}) { setZoomFactorAndSaveEvent({zoomFactor}) {
this.setZoomFactorAndSave(zoomFactor); this.setZoomFactorAndSave(zoomFactor);
} }
} }
const zoomService = new ZoomService();
export default zoomService;

View File

@@ -30,6 +30,11 @@ class BasicWidget extends Component {
return this; return this;
} }
filling() {
this.css('flex-grow', '1');
return this;
}
hideInZenMode() { hideInZenMode() {
this.class('hide-in-zen-mode'); this.class('hide-in-zen-mode');
return this; return this;
@@ -109,4 +114,4 @@ class BasicWidget extends Component {
cleanup() {} cleanup() {}
} }
export default BasicWidget; export default BasicWidget;

View File

@@ -22,15 +22,15 @@ const TPL = `
<tr> <tr>
<th>Note ID:</th> <th>Note ID:</th>
<td nowrap colspan="3" class="note-info-note-id"></td> <td colspan="3" class="note-info-note-id"></td>
</tr> </tr>
<tr> <tr>
<th>Created:</th> <th>Created:</th>
<td nowrap colspan="3" style="overflow: hidden; text-overflow: ellipsis;" class="note-info-date-created"></td> <td colspan="3" class="note-info-date-created"></td>
</tr> </tr>
<tr> <tr>
<th>Modified:</th> <th>Modified:</th>
<td nowrap colspan="3" style="overflow: hidden; text-overflow: ellipsis;" class="note-info-date-modified"></td> <td colspan="3" class="note-info-date-modified"></td>
</tr> </tr>
<tr> <tr>
<th>Type:</th> <th>Type:</th>
@@ -60,11 +60,11 @@ export default class NoteInfoWidget extends CollapsibleWidget {
this.$noteId.text(note.noteId); this.$noteId.text(note.noteId);
this.$dateCreated this.$dateCreated
.text(noteComplement.dateCreated) .text(noteComplement.dateCreated.substr(0, 16))
.attr("title", noteComplement.dateCreated); .attr("title", noteComplement.dateCreated);
this.$dateModified this.$dateModified
.text(noteComplement.dateModified) .text(noteComplement.dateModified.substr(0, 16))
.attr("title", noteComplement.dateCreated); .attr("title", noteComplement.dateCreated);
this.$type.text(note.type); this.$type.text(note.type);

View File

@@ -251,8 +251,8 @@ export default class NoteTreeWidget extends TabAwareWidget {
this.triggerCommand('setActiveScreen', {screen:'detail'}); this.triggerCommand('setActiveScreen', {screen:'detail'});
} }
}, },
expand: (event, data) => this.setExpandedToServer(data.node.data.branchId, true), expand: (event, data) => this.setExpanded(data.node.data.branchId, true),
collapse: (event, data) => this.setExpandedToServer(data.node.data.branchId, false), collapse: (event, data) => this.setExpanded(data.node.data.branchId, false),
hotkeys: utils.isMobile() ? undefined : { keydown: await this.getHotKeys() }, hotkeys: utils.isMobile() ? undefined : { keydown: await this.getHotKeys() },
dnd5: { dnd5: {
autoExpandMS: 600, autoExpandMS: 600,
@@ -804,7 +804,9 @@ export default class NoteTreeWidget extends TabAwareWidget {
async entitiesReloadedEvent({loadResults}) { async entitiesReloadedEvent({loadResults}) {
const activeNode = this.getActiveNode(); const activeNode = this.getActiveNode();
const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null;
const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null; const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
const nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null;
const activeNoteId = activeNode ? activeNode.data.noteId : null; const activeNoteId = activeNode ? activeNode.data.noteId : null;
const noteIdsToUpdate = new Set(); const noteIdsToUpdate = new Set();
@@ -926,15 +928,27 @@ export default class NoteTreeWidget extends TabAwareWidget {
if (node) { if (node) {
node.setActive(true, {noEvents: true}); node.setActive(true, {noEvents: true});
} }
else {
// this is used when original note has been deleted and we want to move the focus to the note above/below
node = await this.expandToNote(nextNotePath);
if (node) {
this.tree.setFocus();
node.setFocus(true);
await appContext.tabManager.getActiveTabContext().setNote(nextNotePath);
}
}
} }
} }
async setExpandedToServer(branchId, isExpanded) { async setExpanded(branchId, isExpanded) {
utils.assertArguments(branchId); utils.assertArguments(branchId);
const expandedNum = isExpanded ? 1 : 0; const branch = treeCache.getBranch(branchId);
branch.isExpanded = isExpanded;
await server.put('branches/' + branchId + '/expanded/' + expandedNum); await server.put(`branches/${branchId}/expanded/${isExpanded ? 1 : 0}`);
} }
async reloadTreeFromCache() { async reloadTreeFromCache() {
@@ -994,7 +1008,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
return false; return false;
} }
}; };
for (const action of actions) { for (const action of actions) {
for (const shortcut of action.effectiveShortcuts) { for (const shortcut of action.effectiveShortcuts) {
hotKeyMap[utils.normalizeShortcut(shortcut)] = node => { hotKeyMap[utils.normalizeShortcut(shortcut)] = node => {
@@ -1019,83 +1033,83 @@ export default class NoteTreeWidget extends TabAwareWidget {
async deleteNotesCommand({node}) { async deleteNotesCommand({node}) {
const branchIds = this.getSelectedOrActiveBranchIds(node); const branchIds = this.getSelectedOrActiveBranchIds(node);
await branchService.deleteNotes(branchIds); await branchService.deleteNotes(branchIds);
this.clearSelectedNodes(); this.clearSelectedNodes();
} }
moveNoteUpCommand({node}) { moveNoteUpCommand({node}) {
const beforeNode = node.getPrevSibling(); const beforeNode = node.getPrevSibling();
if (beforeNode !== null) { if (beforeNode !== null) {
branchService.moveBeforeBranch([node.data.branchId], beforeNode.data.branchId); branchService.moveBeforeBranch([node.data.branchId], beforeNode.data.branchId);
} }
} }
moveNoteDownCommand({node}) { moveNoteDownCommand({node}) {
const afterNode = node.getNextSibling(); const afterNode = node.getNextSibling();
if (afterNode !== null) { if (afterNode !== null) {
branchService.moveAfterBranch([node.data.branchId], afterNode.data.branchId); branchService.moveAfterBranch([node.data.branchId], afterNode.data.branchId);
} }
} }
moveNoteUpInHierarchyCommand({node}) { moveNoteUpInHierarchyCommand({node}) {
branchService.moveNodeUpInHierarchy(node); branchService.moveNodeUpInHierarchy(node);
} }
moveNoteDownInHierarchyCommand({node}) { moveNoteDownInHierarchyCommand({node}) {
const toNode = node.getPrevSibling(); const toNode = node.getPrevSibling();
if (toNode !== null) { if (toNode !== null) {
branchService.moveToParentNote([node.data.branchId], toNode.data.noteId); branchService.moveToParentNote([node.data.branchId], toNode.data.noteId);
} }
} }
addNoteAboveToSelectionCommand() { addNoteAboveToSelectionCommand() {
const node = this.getFocusedNode(); const node = this.getFocusedNode();
if (!node) { if (!node) {
return; return;
} }
if (node.isActive()) { if (node.isActive()) {
node.setSelected(true); node.setSelected(true);
} }
const prevSibling = node.getPrevSibling(); const prevSibling = node.getPrevSibling();
if (prevSibling) { if (prevSibling) {
prevSibling.setActive(true, {noEvents: true}); prevSibling.setActive(true, {noEvents: true});
if (prevSibling.isSelected()) { if (prevSibling.isSelected()) {
node.setSelected(false); node.setSelected(false);
} }
prevSibling.setSelected(true); prevSibling.setSelected(true);
} }
} }
addNoteBelowToSelectionCommand() { addNoteBelowToSelectionCommand() {
const node = this.getFocusedNode(); const node = this.getFocusedNode();
if (!node) { if (!node) {
return; return;
} }
if (node.isActive()) { if (node.isActive()) {
node.setSelected(true); node.setSelected(true);
} }
const nextSibling = node.getNextSibling(); const nextSibling = node.getNextSibling();
if (nextSibling) { if (nextSibling) {
nextSibling.setActive(true, {noEvents: true}); nextSibling.setActive(true, {noEvents: true});
if (nextSibling.isSelected()) { if (nextSibling.isSelected()) {
node.setSelected(false); node.setSelected(false);
} }
nextSibling.setSelected(true); nextSibling.setSelected(true);
} }
} }
@@ -1179,4 +1193,4 @@ export default class NoteTreeWidget extends TabAwareWidget {
noteCreateService.duplicateNote(node.data.noteId, branch.parentNoteId); noteCreateService.duplicateNote(node.data.noteId, branch.parentNoteId);
} }
} }

View File

@@ -19,6 +19,7 @@ const TPL = `
.promoted-attributes td, .promoted-attributes th { .promoted-attributes td, .promoted-attributes th {
padding: 5px; padding: 5px;
min-width: 50px; /* otherwise checkboxes can collapse into 0 width (if there are only checkboxes) */
} }
</style> </style>
@@ -98,7 +99,7 @@ export default class PromotedAttributesWidget extends TabAwareWidget {
const $labelCell = $("<th>").append(valueAttr.name); const $labelCell = $("<th>").append(valueAttr.name);
const $input = $("<input>") const $input = $("<input>")
.prop("tabindex", definitionAttr.position) .prop("tabindex", definitionAttr.position)
.prop("attribute-id", valueAttr.isOwned ? valueAttr.attributeId : '') // if not owned, we'll force creation of a new attribute instead of updating the inherited one .prop("attribute-id", valueAttr.noteId === this.noteId ? valueAttr.attributeId : '') // if not owned, we'll force creation of a new attribute instead of updating the inherited one
.prop("attribute-type", valueAttr.type) .prop("attribute-type", valueAttr.type)
.prop("attribute-name", valueAttr.name) .prop("attribute-name", valueAttr.name)
.prop("value", valueAttr.value) .prop("value", valueAttr.value)
@@ -266,4 +267,4 @@ export default class PromotedAttributesWidget extends TabAwareWidget {
$attr.prop("attribute-id", result.attributeId); $attr.prop("attribute-id", result.attributeId);
} }
} }

View File

@@ -602,18 +602,23 @@ export default class TabRowWidget extends BasicWidget {
} }
updateTab($tab, note) { updateTab($tab, note) {
if (!note || !$tab.length) { if (!$tab.length) {
return; return;
} }
this.updateTitle($tab, note.title);
for (const clazz of Array.from($tab[0].classList)) { // create copy to safely iterate over while removing classes for (const clazz of Array.from($tab[0].classList)) { // create copy to safely iterate over while removing classes
if (clazz !== 'note-tab') { if (clazz !== 'note-tab') {
$tab.removeClass(clazz); $tab.removeClass(clazz);
} }
} }
if (!note) {
this.updateTitle($tab, 'New tab');
return;
}
this.updateTitle($tab, note.title);
$tab.addClass(note.getCssClass()); $tab.addClass(note.getCssClass());
$tab.addClass(utils.getNoteTypeClass(note.type)); $tab.addClass(utils.getNoteTypeClass(note.type));
$tab.addClass(utils.getMimeTypeClass(note.mime)); $tab.addClass(utils.getMimeTypeClass(note.mime));
@@ -636,4 +641,4 @@ export default class TabRowWidget extends BasicWidget {
this.updateTab($tab, tabContext.note); this.updateTab($tab, tabContext.note);
} }
} }
} }

View File

@@ -22,6 +22,10 @@ const TPL = `
.note-detail-readonly-text p:first-child, .note-detail-text::before { .note-detail-readonly-text p:first-child, .note-detail-text::before {
margin-top: 0; margin-top: 0;
} }
.note-detail-readonly-text img {
max-width: 100%;
}
</style> </style>
<div class="alert alert-warning no-print"> <div class="alert alert-warning no-print">
@@ -77,4 +81,4 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
this.loadIncludedNote(noteId, $(el)); this.loadIncludedNote(noteId, $(el));
}); });
} }
} }

View File

@@ -199,4 +199,4 @@ module.exports = {
getEffectiveNoteAttributes, getEffectiveNoteAttributes,
createRelation, createRelation,
deleteRelation deleteRelation
}; };

View File

@@ -1,6 +1,5 @@
"use strict"; "use strict";
const noteService = require('../../services/notes');
const protectedSessionService = require('../../services/protected_session'); const protectedSessionService = require('../../services/protected_session');
const repository = require('../../services/repository'); const repository = require('../../services/repository');
const utils = require('../../services/utils'); const utils = require('../../services/utils');
@@ -45,7 +44,9 @@ async function downloadNoteFile(noteId, res, contentDisposition = true) {
if (contentDisposition) { if (contentDisposition) {
// (one) reason we're not using the originFileName (available as label) is that it's not // (one) reason we're not using the originFileName (available as label) is that it's not
// available for older note revisions and thus would be inconsistent // available for older note revisions and thus would be inconsistent
res.setHeader('Content-Disposition', utils.getContentDisposition(note.title || "untitled")); const filename = utils.formatDownloadTitle(note.title, note.type, note.mime);
res.setHeader('Content-Disposition', utils.getContentDisposition(filename));
} }
res.setHeader('Content-Type', note.mime); res.setHeader('Content-Type', note.mime);
@@ -70,4 +71,4 @@ module.exports = {
openFile, openFile,
downloadFile, downloadFile,
downloadNoteFile downloadNoteFile
}; };

View File

@@ -3,6 +3,7 @@
const repository = require('../../services/repository'); const repository = require('../../services/repository');
const noteCacheService = require('../../services/note_cache'); const noteCacheService = require('../../services/note_cache');
const protectedSessionService = require('../../services/protected_session'); const protectedSessionService = require('../../services/protected_session');
const noteRevisionService = require('../../services/note_revisions');
const utils = require('../../services/utils'); const utils = require('../../services/utils');
const path = require('path'); const path = require('path');
@@ -37,13 +38,7 @@ async function getNoteRevision(req) {
* @return {string} * @return {string}
*/ */
function getRevisionFilename(noteRevision) { function getRevisionFilename(noteRevision) {
let filename = noteRevision.title || "untitled"; let filename = utils.formatDownloadTitle(noteRevision.title, noteRevision.type, noteRevision.mime);
if (noteRevision.type === 'text') {
filename += '.html';
} else if (['relation-map', 'search'].includes(noteRevision.type)) {
filename += '.json';
}
const extension = path.extname(filename); const extension = path.extname(filename);
const date = noteRevision.dateCreated const date = noteRevision.dateCreated
@@ -109,6 +104,20 @@ async function eraseNoteRevision(req) {
} }
} }
async function restoreNoteRevision(req) {
const noteRevision = await repository.getNoteRevision(req.params.noteRevisionId);
if (noteRevision && !noteRevision.isErased) {
const note = await noteRevision.getNote();
await noteRevisionService.createNoteRevision(note);
note.title = noteRevision.title;
await note.setContent(await noteRevision.getContent());
await note.save();
}
}
async function getEditedNotesOnDate(req) { async function getEditedNotesOnDate(req) {
const date = utils.sanitizeSql(req.params.date); const date = utils.sanitizeSql(req.params.date);
@@ -141,5 +150,6 @@ module.exports = {
downloadNoteRevision, downloadNoteRevision,
getEditedNotesOnDate, getEditedNotesOnDate,
eraseAllNoteRevisions, eraseAllNoteRevisions,
eraseNoteRevision eraseNoteRevision,
}; restoreNoteRevision
};

View File

@@ -145,6 +145,7 @@ function register(app) {
apiRoute(GET, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.getNoteRevision); apiRoute(GET, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.getNoteRevision);
apiRoute(DELETE, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.eraseNoteRevision); apiRoute(DELETE, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.eraseNoteRevision);
route(GET, '/api/notes/:noteId/revisions/:noteRevisionId/download', [auth.checkApiAuthOrElectron], noteRevisionsApiRoute.downloadNoteRevision); route(GET, '/api/notes/:noteId/revisions/:noteRevisionId/download', [auth.checkApiAuthOrElectron], noteRevisionsApiRoute.downloadNoteRevision);
apiRoute(PUT, '/api/notes/:noteId/restore-revision/:noteRevisionId', noteRevisionsApiRoute.restoreNoteRevision);
apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap); apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap);
apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle); apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle);
apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateNote); apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateNote);

View File

@@ -6,9 +6,8 @@ const utils = require('../services/utils');
async function setupPage(req, res) { async function setupPage(req, res) {
if (await sqlInit.isDbInitialized()) { if (await sqlInit.isDbInitialized()) {
const windowService = require('../services/window');
if (utils.isElectron()) { if (utils.isElectron()) {
const windowService = require('../services/window');
await windowService.createMainWindow(); await windowService.createMainWindow();
windowService.closeSetupWindow(); windowService.closeSetupWindow();
} }

View File

@@ -1 +1 @@
module.exports = { buildDate:"2020-05-06T23:24:13+02:00", buildRevision: "54ecd2ee75d1177cedadf9fee10319687feee5f0" }; module.exports = { buildDate:"2020-05-12T16:46:45+02:00", buildRevision: "4f50864ec8346a12d7845cb4c91a3de3b1043d34" };

View File

@@ -13,6 +13,7 @@ const Attribute = require('../entities/attribute');
const hoistedNoteService = require('../services/hoisted_note'); const hoistedNoteService = require('../services/hoisted_note');
const protectedSessionService = require('../services/protected_session'); const protectedSessionService = require('../services/protected_session');
const log = require('../services/log'); const log = require('../services/log');
const utils = require('../services/utils');
const noteRevisionService = require('../services/note_revisions'); const noteRevisionService = require('../services/note_revisions');
const attributeService = require('../services/attributes'); const attributeService = require('../services/attributes');
const request = require('./request'); const request = require('./request');
@@ -276,9 +277,9 @@ async function downloadImage(noteId, imageUrl) {
const downloadImagePromises = {}; const downloadImagePromises = {};
function replaceUrl(content, url, imageNote) { function replaceUrl(content, url, imageNote) {
const quoted = url.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); const quotedUrl = utils.quoteRegex(url);
return content.replace(new RegExp(`\\s+src=[\"']${quoted}[\"']`, "g"), ` src="api/images/${imageNote.noteId}/${imageNote.title}"`); return content.replace(new RegExp(`\\s+src=[\"']${quotedUrl}[\"']`, "g"), ` src="api/images/${imageNote.noteId}/${imageNote.title}"`);
} }
async function downloadImages(noteId, content) { async function downloadImages(noteId, content) {

View File

@@ -1,6 +1,6 @@
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const filterRegex = /(\b(AND|OR)\s+)?@(!?)([\p{L}\p{Number}_]+|"[^"]+")\s*((=|!=|<|<=|>|>=|!?\*=|!?=\*|!?\*=\*)\s*([^\s=*]+|"[^"]+"))?/igu; const filterRegex = /(\b(AND|OR)\s+)?@(!?)([\p{L}\p{Number}_]+|"[^"]+")\s*((=|!=|<|<=|>|>=|!?\*=|!?=\*|!?\*=\*)\s*([^\s=*"]+|"[^"]+"))?/igu;
const smartValueRegex = /^(NOW|TODAY|WEEK|MONTH|YEAR) *([+\-] *\d+)?$/i; const smartValueRegex = /^(NOW|TODAY|WEEK|MONTH|YEAR) *([+\-] *\d+)?$/i;
function calculateSmartValue(v) { function calculateSmartValue(v) {

View File

@@ -221,6 +221,7 @@ async function transactional(func) {
await commit(); await commit();
// note that sync rows sent from this action will be sent again by scheduled periodic ping
require('./ws.js').sendPingToAllClients(); require('./ws.js').sendPingToAllClients();
transactionActive = false; transactionActive = false;
@@ -267,4 +268,4 @@ module.exports = {
executeScript, executeScript,
transactional, transactional,
upsert upsert
}; };

View File

@@ -5,6 +5,7 @@ const randtoken = require('rand-token').generator({source: 'crypto'});
const unescape = require('unescape'); const unescape = require('unescape');
const escape = require('escape-html'); const escape = require('escape-html');
const sanitize = require("sanitize-filename"); const sanitize = require("sanitize-filename");
const mimeTypes = require('mime-types');
function newEntityId() { function newEntityId() {
return randomString(12); return randomString(12);
@@ -166,10 +167,46 @@ function isStringNote(type, mime) {
|| STRING_MIME_TYPES.includes(mime); || STRING_MIME_TYPES.includes(mime);
} }
function replaceAll(string, replaceWhat, replaceWith) { function quoteRegex(url) {
const escapedWhat = replaceWhat.replace(/([\/,!\\^${}\[\]().*+?|<>\-&])/g, "\\$&"); return url.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
}
return string.replace(new RegExp(escapedWhat, "g"), replaceWith); function replaceAll(string, replaceWhat, replaceWith) {
const quotedReplaceWhat = quoteRegex(replaceWhat);
return string.replace(new RegExp(quotedReplaceWhat, "g"), replaceWith);
}
function formatDownloadTitle(filename, type, mime) {
if (!filename) {
filename = "untitled";
}
if (type === 'text') {
return filename + '.html';
} else if (['relation-map', 'search'].includes(type)) {
return filename + '.json';
} else {
if (!mime) {
return filename;
}
mime = mime.toLowerCase();
const filenameLc = filename.toLowerCase();
const extensions = mimeTypes.extensions[mime];
if (!extensions || extensions.length === 0) {
return filename;
}
for (const ext of extensions) {
if (filenameLc.endsWith('.' + ext)) {
return filename;
}
}
return filename + '.' + extensions[0];
}
} }
module.exports = { module.exports = {
@@ -198,5 +235,7 @@ module.exports = {
sanitizeFilenameForHeader, sanitizeFilenameForHeader,
getContentDisposition, getContentDisposition,
isStringNote, isStringNote,
replaceAll quoteRegex,
}; replaceAll,
formatDownloadTitle
};

View File

@@ -165,6 +165,5 @@ module.exports = {
createMainWindow, createMainWindow,
createSetupWindow, createSetupWindow,
closeSetupWindow, closeSetupWindow,
createExtraWindow,
registerGlobalShortcuts registerGlobalShortcuts
}; };

View File

@@ -5,7 +5,7 @@
<link rel="shortcut icon" href="favicon.ico"> <link rel="shortcut icon" href="favicon.ico">
<title>Trilium Notes</title> <title>Trilium Notes</title>
</head> </head>
<body class="desktop theme-<%= theme %>" style="display: none; --main-font-size: <%= mainFontSize %>%; --tree-font-size: <%= treeFontSize %>%; --detail-font-size: <%= detailFontSize %>%;"> <body class="desktop theme-<%= theme %>" style="--main-font-size: <%= mainFontSize %>%; --tree-font-size: <%= treeFontSize %>%; --detail-font-size: <%= detailFontSize %>%;">
<noscript>Trilium requires JavaScript to be enabled.</noscript> <noscript>Trilium requires JavaScript to be enabled.</noscript>
<div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div> <div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div>
@@ -83,9 +83,5 @@
<link rel="stylesheet" type="text/css" href="libraries/boxicons/css/boxicons.min.css"> <link rel="stylesheet" type="text/css" href="libraries/boxicons/css/boxicons.min.css">
<script>
$("body").show();
</script>
</body> </body>
</html> </html>

View File

@@ -210,7 +210,7 @@
<script src="libraries/knockout.min.js"></script> <script src="libraries/knockout.min.js"></script>
<script src="app-dist/setup.js" crossorigin type="module"></script> <script src="app/setup.js" crossorigin type="module"></script>
<link href="stylesheets/themes.css" rel="stylesheet"> <link href="stylesheets/themes.css" rel="stylesheet">
</body> </body>
</html> </html>