Compare commits

...

20 Commits

Author SHA1 Message Date
zadam
5769587305 experimental hiding of images if they are included in the parent note 2020-04-27 23:27:45 +02:00
zadam
ffbfccb701 extra window now works in browsers too 2020-04-27 22:29:39 +02:00
zadam
907cdd8fcb fixes for extra window 2020-04-26 23:11:52 +02:00
zadam
7ea53d468e use local dates in the recent changes 2020-04-26 14:39:13 +02:00
zadam
586d6b4557 in web version use local client time instead of server time for recording dateModified etc. 2020-04-26 14:26:57 +02:00
zadam
8f68ff1932 tweaks for the code preview 2020-04-26 12:06:54 +02:00
zadam
a1ea2c9115 read only code notes WIP 2020-04-26 11:38:30 +02:00
zadam
e8ce81a133 organize widgets a bit 2020-04-26 09:40:02 +02:00
zadam
aff12950f0 Merge remote-tracking branch 'origin/master' into m42
# Conflicts:
#	src/public/app/services/app_context.js
2020-04-25 23:53:19 +02:00
zadam
75c58cbf79 refactored layouts for extra window 2020-04-25 23:52:13 +02:00
zadam
87a1e98fa2 default search should look also into attribute names and values, #980 2020-04-25 22:10:56 +02:00
zadam
d1eacbb574 more robust entering protected session and the following protection of a note 2020-04-25 17:15:57 +02:00
zadam
71d248cd87 touch protected session during note update 2020-04-25 11:09:07 +02:00
zadam
ac608b9334 small text changes 2020-04-24 21:21:22 +02:00
zadam
32020d78b5 prototype for new app window 2020-04-23 23:08:15 +02:00
zadam
ff853c7d0a implement lazy loading of tabs which speeds up especially initial startup with many tabs 2020-04-22 23:09:35 +02:00
zadam
8526cb2315 added collapsible widgets to the docs 2020-04-21 23:14:55 +02:00
zadam
dc89f72e75 fix display of text notes in note revisions 2020-04-21 22:59:37 +02:00
zadam
657ff16267 fix build webpack 2020-04-20 23:14:50 +02:00
zadam
ed759f5585 release 0.41.5 2020-04-20 22:40:02 +02:00
73 changed files with 1308 additions and 501 deletions

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<dataSource name="document.db"> <dataSource name="document.db">
<database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.17"> <database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.18">
<root id="1"> <root id="1">
<ServerVersion>3.25.1</ServerVersion> <ServerVersion>3.16.1</ServerVersion>
</root> </root>
<schema id="2" parent="1" name="main"> <schema id="2" parent="1" name="main">
<Current>1</Current> <Current>1</Current>
@@ -57,6 +57,7 @@
<index id="24" parent="6" name="sqlite_autoindex_api_tokens_1"> <index id="24" parent="6" name="sqlite_autoindex_api_tokens_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>apiTokenId</ColNames> <ColNames>apiTokenId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<key id="25" parent="6"> <key id="25" parent="6">
@@ -130,17 +131,21 @@
<index id="38" parent="7" name="sqlite_autoindex_attributes_1"> <index id="38" parent="7" name="sqlite_autoindex_attributes_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>attributeId</ColNames> <ColNames>attributeId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<index id="39" parent="7" name="IDX_attributes_noteId_index"> <index id="39" parent="7" name="IDX_attributes_noteId_index">
<ColNames>noteId</ColNames> <ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<index id="40" parent="7" name="IDX_attributes_name_value"> <index id="40" parent="7" name="IDX_attributes_name_value">
<ColNames>name <ColNames>name
value</ColNames> value</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<index id="41" parent="7" name="IDX_attributes_value_index"> <index id="41" parent="7" name="IDX_attributes_value_index">
<ColNames>value</ColNames> <ColNames>value</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<key id="42" parent="7"> <key id="42" parent="7">
<ColNames>attributeId</ColNames> <ColNames>attributeId</ColNames>
@@ -207,14 +212,17 @@ value</ColNames>
<index id="54" parent="8" name="sqlite_autoindex_branches_1"> <index id="54" parent="8" name="sqlite_autoindex_branches_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>branchId</ColNames> <ColNames>branchId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<index id="55" parent="8" name="IDX_branches_noteId_parentNoteId"> <index id="55" parent="8" name="IDX_branches_noteId_parentNoteId">
<ColNames>noteId <ColNames>noteId
parentNoteId</ColNames> parentNoteId</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<index id="56" parent="8" name="IDX_branches_parentNoteId"> <index id="56" parent="8" name="IDX_branches_parentNoteId">
<ColNames>parentNoteId</ColNames> <ColNames>parentNoteId</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<key id="57" parent="8"> <key id="57" parent="8">
<ColNames>branchId</ColNames> <ColNames>branchId</ColNames>
@@ -245,6 +253,7 @@ parentNoteId</ColNames>
<index id="62" parent="9" name="sqlite_autoindex_note_contents_1"> <index id="62" parent="9" name="sqlite_autoindex_note_contents_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>noteId</ColNames> <ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<key id="63" parent="9"> <key id="63" parent="9">
@@ -275,6 +284,7 @@ parentNoteId</ColNames>
<index id="68" parent="10" name="sqlite_autoindex_note_revision_contents_1"> <index id="68" parent="10" name="sqlite_autoindex_note_revision_contents_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>noteRevisionId</ColNames> <ColNames>noteRevisionId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<key id="69" parent="10"> <key id="69" parent="10">
@@ -359,22 +369,28 @@ parentNoteId</ColNames>
<index id="84" parent="11" name="sqlite_autoindex_note_revisions_1"> <index id="84" parent="11" name="sqlite_autoindex_note_revisions_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>noteRevisionId</ColNames> <ColNames>noteRevisionId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<index id="85" parent="11" name="IDX_note_revisions_noteId"> <index id="85" parent="11" name="IDX_note_revisions_noteId">
<ColNames>noteId</ColNames> <ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<index id="86" parent="11" name="IDX_note_revisions_utcDateLastEdited"> <index id="86" parent="11" name="IDX_note_revisions_utcDateLastEdited">
<ColNames>utcDateLastEdited</ColNames> <ColNames>utcDateLastEdited</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<index id="87" parent="11" name="IDX_note_revisions_utcDateCreated"> <index id="87" parent="11" name="IDX_note_revisions_utcDateCreated">
<ColNames>utcDateCreated</ColNames> <ColNames>utcDateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<index id="88" parent="11" name="IDX_note_revisions_dateLastEdited"> <index id="88" parent="11" name="IDX_note_revisions_dateLastEdited">
<ColNames>dateLastEdited</ColNames> <ColNames>dateLastEdited</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<index id="89" parent="11" name="IDX_note_revisions_dateCreated"> <index id="89" parent="11" name="IDX_note_revisions_dateCreated">
<ColNames>dateCreated</ColNames> <ColNames>dateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<key id="90" parent="11"> <key id="90" parent="11">
<ColNames>noteRevisionId</ColNames> <ColNames>noteRevisionId</ColNames>
@@ -461,28 +477,36 @@ parentNoteId</ColNames>
<index id="105" parent="12" name="sqlite_autoindex_notes_1"> <index id="105" parent="12" name="sqlite_autoindex_notes_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>noteId</ColNames> <ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<index id="106" parent="12" name="IDX_notes_title"> <index id="106" parent="12" name="IDX_notes_title">
<ColNames>title</ColNames> <ColNames>title</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<index id="107" parent="12" name="IDX_notes_type"> <index id="107" parent="12" name="IDX_notes_type">
<ColNames>type</ColNames> <ColNames>type</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<index id="108" parent="12" name="IDX_notes_isDeleted"> <index id="108" parent="12" name="IDX_notes_isDeleted">
<ColNames>isDeleted</ColNames> <ColNames>isDeleted</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<index id="109" parent="12" name="IDX_notes_dateCreated"> <index id="109" parent="12" name="IDX_notes_dateCreated">
<ColNames>dateCreated</ColNames> <ColNames>dateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<index id="110" parent="12" name="IDX_notes_dateModified"> <index id="110" parent="12" name="IDX_notes_dateModified">
<ColNames>dateModified</ColNames> <ColNames>dateModified</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<index id="111" parent="12" name="IDX_notes_utcDateCreated"> <index id="111" parent="12" name="IDX_notes_utcDateCreated">
<ColNames>utcDateCreated</ColNames> <ColNames>utcDateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<index id="112" parent="12" name="IDX_notes_utcDateModified"> <index id="112" parent="12" name="IDX_notes_utcDateModified">
<ColNames>utcDateModified</ColNames> <ColNames>utcDateModified</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<key id="113" parent="12"> <key id="113" parent="12">
<ColNames>noteId</ColNames> <ColNames>noteId</ColNames>
@@ -523,6 +547,7 @@ parentNoteId</ColNames>
<index id="120" parent="13" name="sqlite_autoindex_options_1"> <index id="120" parent="13" name="sqlite_autoindex_options_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>name</ColNames> <ColNames>name</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<key id="121" parent="13"> <key id="121" parent="13">
@@ -558,6 +583,7 @@ parentNoteId</ColNames>
<index id="127" parent="14" name="sqlite_autoindex_recent_notes_1"> <index id="127" parent="14" name="sqlite_autoindex_recent_notes_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>noteId</ColNames> <ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<key id="128" parent="14"> <key id="128" parent="14">
@@ -578,10 +604,12 @@ parentNoteId</ColNames>
<index id="131" parent="15" name="sqlite_autoindex_source_ids_1"> <index id="131" parent="15" name="sqlite_autoindex_source_ids_1">
<NameSurrogate>1</NameSurrogate> <NameSurrogate>1</NameSurrogate>
<ColNames>sourceId</ColNames> <ColNames>sourceId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<index id="132" parent="15" name="IDX_source_ids_utcDateCreated"> <index id="132" parent="15" name="IDX_source_ids_utcDateCreated">
<ColNames>utcDateCreated</ColNames> <ColNames>utcDateCreated</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<key id="133" parent="15"> <key id="133" parent="15">
<ColNames>sourceId</ColNames> <ColNames>sourceId</ColNames>
@@ -602,7 +630,7 @@ parentNoteId</ColNames>
</column> </column>
<column id="137" parent="16" name="rootpage"> <column id="137" parent="16" name="rootpage">
<Position>4</Position> <Position>4</Position>
<DataType>int|0s</DataType> <DataType>integer|0s</DataType>
</column> </column>
<column id="138" parent="16" name="sql"> <column id="138" parent="16" name="sql">
<Position>5</Position> <Position>5</Position>
@@ -649,10 +677,12 @@ parentNoteId</ColNames>
<index id="147" parent="18" name="IDX_sync_entityName_entityId"> <index id="147" parent="18" name="IDX_sync_entityName_entityId">
<ColNames>entityName <ColNames>entityName
entityId</ColNames> entityId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique> <Unique>1</Unique>
</index> </index>
<index id="148" parent="18" name="IDX_sync_utcSyncDate"> <index id="148" parent="18" name="IDX_sync_utcSyncDate">
<ColNames>utcSyncDate</ColNames> <ColNames>utcSyncDate</ColNames>
<ColumnCollations></ColumnCollations>
</index> </index>
<key id="149" parent="18"> <key id="149" parent="18">
<ColNames>id</ColNames> <ColNames>id</ColNames>

1
.idea/vcs.xml generated
View File

@@ -2,5 +2,6 @@
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" /> <mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component> </component>
</project> </project>

View File

@@ -5,6 +5,8 @@ if [[ $# -eq 0 ]] ; then
exit 1 exit 1
fi fi
npm run webpack
DIR=$1 DIR=$1
rm -rf $DIR rm -rf $DIR
@@ -25,7 +27,7 @@ cp -r electron.js $DIR/
cp webpack-* $DIR/ cp webpack-* $DIR/
# run in subshell (so we return to original dir) # run in subshell (so we return to original dir)
(cd $DIR && npm install --only=prod && npm run webpack) (cd $DIR && npm install --only=prod)
find $DIR/libraries -name "*.map" -type f -delete find $DIR/libraries -name "*.map" -type f -delete

View File

@@ -879,7 +879,7 @@
</div> </div>
<nav> <nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
</nav> </nav>
<br class="clear"> <br class="clear">

View File

@@ -5483,7 +5483,7 @@ Typical use case is when new note has been created, we should wait until it is s
</div> </div>
<nav> <nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
</nav> </nav>
<br class="clear"> <br class="clear">

View File

@@ -507,7 +507,7 @@
</div> </div>
<nav> <nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
</nav> </nav>
<br class="clear"> <br class="clear">

View File

@@ -6813,7 +6813,7 @@ Cache is note instance scoped.
</div> </div>
<nav> <nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
</nav> </nav>
<br class="clear"> <br class="clear">

View File

@@ -79,7 +79,7 @@ export default Attribute;</code></pre>
</div> </div>
<nav> <nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
</nav> </nav>
<br class="clear"> <br class="clear">

View File

@@ -81,7 +81,7 @@ export default Branch;</code></pre>
</div> </div>
<nav> <nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
</nav> </nav>
<br class="clear"> <br class="clear">

View File

@@ -61,7 +61,7 @@ export default NoteComplement;</code></pre>
</div> </div>
<nav> <nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
</nav> </nav>
<br class="clear"> <br class="clear">

View File

@@ -493,7 +493,7 @@ export default NoteShort;</code></pre>
</div> </div>
<nav> <nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
</nav> </nav>
<br class="clear"> <br class="clear">

View File

@@ -95,6 +95,275 @@
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="decorateWidget"><span class="type-signature"></span>decorateWidget<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
for overriding
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="widgets_collapsible_widget.js.html">widgets/collapsible_widget.js</a>, <a href="widgets_collapsible_widget.js.html#line93">line 93</a>
</li></ul></dd>
</dl>
<h4 class="name" id="doRenderBody"><span class="type-signature">(async) </span>doRenderBody<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
for overriding
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="widgets_collapsible_widget.js.html">widgets/collapsible_widget.js</a>, <a href="widgets_collapsible_widget.js.html#line96">line 96</a>
</li></ul></dd>
</dl>
<h4 class="name" id="widgetCollapsedStateChangedEvent"><span class="type-signature"></span>widgetCollapsedStateChangedEvent<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
This event is used to synchronize collapsed state of all the tab-cached widgets since they are all rendered
separately but should behave uniformly for the user.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="widgets_collapsible_widget.js.html">widgets/collapsible_widget.js</a>, <a href="widgets_collapsible_widget.js.html#line86">line 86</a>
</li></ul></dd>
</dl>
@@ -333,7 +602,7 @@
</div> </div>
<nav> <nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
</nav> </nav>
<br class="clear"> <br class="clear">

View File

@@ -50,7 +50,7 @@
</div> </div>
<nav> <nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
</nav> </nav>
<br class="clear"> <br class="clear">

View File

@@ -443,7 +443,7 @@ export default FrontendScriptApi;</code></pre>
</div> </div>
<nav> <nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
</nav> </nav>
<br class="clear"> <br class="clear">

View File

@@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: widgets/collapsible_widget.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: widgets/collapsible_widget.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>import TabAwareWidget from "./tab_aware_widget.js";
import options from "../services/options.js";
const WIDGET_TPL = `
&lt;div class="card widget">
&lt;div class="card-header">
&lt;div>
&lt;button class="btn btn-sm widget-title" data-toggle="collapse" data-target="#[to be set]">
Collapsible Group Item
&lt;/button>
&lt;a class="widget-help external no-arrow bx bx-info-circle">&lt;/a>
&lt;/div>
&lt;div class="widget-header-actions">&lt;/div>
&lt;/div>
&lt;div id="[to be set]" class="collapse body-wrapper" style="transition: none; ">
&lt;div class="card-body">&lt;/div>
&lt;/div>
&lt;/div>`;
export default class CollapsibleWidget extends TabAwareWidget {
get widgetTitle() { return "Untitled widget"; }
get headerActions() { return []; }
get help() { return {}; }
doRender() {
this.$widget = $(WIDGET_TPL);
this.$widget.find('[data-target]').attr('data-target', "#" + this.componentId);
this.$bodyWrapper = this.$widget.find('.body-wrapper');
this.$bodyWrapper.attr('id', this.componentId); // for toggle to work we need id
this.widgetName = this.constructor.name;
if (!options.is(this.widgetName + 'Collapsed')) {
this.$bodyWrapper.collapse("show");
}
// using immediate variants of the event so that the previous collapse is not caught
this.$bodyWrapper.on('hide.bs.collapse', () => this.saveCollapsed(true));
this.$bodyWrapper.on('show.bs.collapse', () => this.saveCollapsed(false));
this.$body = this.$bodyWrapper.find('.card-body');
this.$title = this.$widget.find('.widget-title');
this.$title.text(this.widgetTitle);
this.$help = this.$widget.find('.widget-help');
if (this.help.title) {
this.$help.attr("title", this.help.title);
this.$help.attr("href", this.help.url || "javascript:");
if (!this.help.url) {
this.$help.addClass('no-link');
}
}
else {
this.$help.hide();
}
this.$headerActions = this.$widget.find('.widget-header-actions');
this.$headerActions.append(...this.headerActions);
this.initialized = this.doRenderBody();
this.decorateWidget();
return this.$widget;
}
saveCollapsed(collapse) {
options.save(this.widgetName + 'Collapsed', collapse.toString());
this.triggerEvent(`widgetCollapsedStateChanged`, {widgetName: this.widgetName, collapse});
}
/**
* This event is used to synchronize collapsed state of all the tab-cached widgets since they are all rendered
* separately but should behave uniformly for the user.
*/
widgetCollapsedStateChangedEvent({widgetName, collapse}) {
if (widgetName === this.widgetName) {
this.$bodyWrapper.toggleClass('show', !collapse);
}
}
/** for overriding */
decorateWidget() {}
/** for overriding */
async doRenderBody() {}
isExpanded() {
return this.$bodyWrapper.hasClass("show");
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteComplement.html">NoteComplement</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3>Global</h3><ul><li><a href="global.html#decorateWidget">decorateWidget</a></li><li><a href="global.html#doRenderBody">doRenderBody</a></li><li><a href="global.html#widgetCollapsedStateChangedEvent">widgetCollapsedStateChangedEvent</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.4</a>
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

532
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@
"start-server": "TRILIUM_ENV=dev node ./src/www", "start-server": "TRILIUM_ENV=dev node ./src/www",
"start-electron": "TRILIUM_ENV=dev electron .", "start-electron": "TRILIUM_ENV=dev electron .",
"build-backend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js", "build-backend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js",
"build-frontend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js", "build-frontend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/collapsible_widget.js",
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs", "build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
"webpack": "npx webpack -c webpack-desktop.config.js && npx webpack -c webpack-mobile.config.js && npx webpack -c webpack-setup.config.js" "webpack": "npx webpack -c webpack-desktop.config.js && npx webpack -c webpack-mobile.config.js && npx webpack -c webpack-setup.config.js"
}, },
@@ -28,16 +28,16 @@
"commonmark": "0.29.1", "commonmark": "0.29.1",
"cookie-parser": "1.4.5", "cookie-parser": "1.4.5",
"csurf": "1.11.0", "csurf": "1.11.0",
"dayjs": "1.8.24", "dayjs": "1.8.25",
"debug": "4.1.1", "debug": "4.1.1",
"ejs": "3.0.2", "ejs": "3.1.2",
"electron-debug": "3.0.1", "electron-debug": "3.0.1",
"electron-dl": "3.0.0", "electron-dl": "3.0.0",
"electron-find": "1.0.6", "electron-find": "1.0.6",
"electron-window-state": "5.0.3", "electron-window-state": "5.0.3",
"express": "4.17.1", "express": "4.17.1",
"express-session": "1.17.1", "express-session": "1.17.1",
"file-type": "14.1.4", "file-type": "14.2.0",
"fs-extra": "9.0.0", "fs-extra": "9.0.0",
"helmet": "3.22.0", "helmet": "3.22.0",
"html": "1.0.0", "html": "1.0.0",
@@ -51,10 +51,10 @@
"imagemin-pngquant": "8.0.0", "imagemin-pngquant": "8.0.0",
"ini": "1.3.5", "ini": "1.3.5",
"is-svg": "4.2.1", "is-svg": "4.2.1",
"jimp": "0.10.2", "jimp": "0.10.3",
"mime-types": "2.1.26", "mime-types": "2.1.27",
"multer": "1.4.2", "multer": "1.4.2",
"node-abi": "2.15.0", "node-abi": "2.16.0",
"open": "7.0.3", "open": "7.0.3",
"portscanner": "2.2.0", "portscanner": "2.2.0",
"rand-token": "1.0.1", "rand-token": "1.0.1",
@@ -73,18 +73,18 @@
"turndown": "6.0.0", "turndown": "6.0.0",
"turndown-plugin-gfm": "1.0.2", "turndown-plugin-gfm": "1.0.2",
"unescape": "1.0.1", "unescape": "1.0.1",
"ws": "7.2.3", "ws": "7.2.5",
"yauzl": "^2.10.0", "yauzl": "^2.10.0",
"yazl": "^2.5.1" "yazl": "^2.5.1"
}, },
"devDependencies": { "devDependencies": {
"electron": "9.0.0-beta.18", "electron": "9.0.0-beta.20",
"electron-builder": "22.5.1", "electron-builder": "22.5.1",
"electron-packager": "14.2.1", "electron-packager": "14.2.1",
"electron-rebuild": "1.10.1", "electron-rebuild": "1.10.1",
"jsdoc": "3.6.4", "jsdoc": "3.6.4",
"lorem-ipsum": "2.0.3", "lorem-ipsum": "2.0.3",
"webpack": "5.0.0-beta.14", "webpack": "5.0.0-beta.15",
"webpack-cli": "4.0.0-beta.8" "webpack-cli": "4.0.0-beta.8"
}, },
"optionalDependencies": { "optionalDependencies": {

View File

@@ -5,8 +5,9 @@ import bundleService from "./services/bundle.js";
import noteAutocompleteService from './services/note_autocomplete.js'; import noteAutocompleteService from './services/note_autocomplete.js';
import macInit from './services/mac_init.js'; import macInit from './services/mac_init.js';
import contextMenu from "./services/context_menu.js"; import contextMenu from "./services/context_menu.js";
import DesktopLayout from "./widgets/desktop_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";
glob.setupGlobs(); glob.setupGlobs();
@@ -23,9 +24,11 @@ $('[data-toggle="tooltip"]').tooltip({
macInit.init(); macInit.init();
bundleService.getWidgetBundlesByParent().then(widgetBundles => { bundleService.getWidgetBundlesByParent().then(widgetBundles => {
const desktopLayout = new DesktopLayout(widgetBundles); const layout = window.glob.isMainWindow
? new DesktopMainWindowLayout(widgetBundles)
: new DesktopExtraWindowLayout(widgetBundles);
appContext.setLayout(desktopLayout); appContext.setLayout(layout);
appContext.start(); appContext.start();
}); });

View File

@@ -32,10 +32,10 @@ export async function showDialog(ancestorNoteId) {
for (const [dateDay, dayChanges] of groupedByDate) { for (const [dateDay, dayChanges] of groupedByDate) {
const $changesList = $('<ul>'); const $changesList = $('<ul>');
const dayEl = $('<div>').append($('<b>').html(utils.formatDate(dateDay))).append($changesList); const dayEl = $('<div>').append($('<b>').text(dateDay)).append($changesList);
for (const change of dayChanges) { for (const change of dayChanges) {
const formattedTime = utils.formatTime(utils.parseDate(change.date)); const formattedTime = change.date.substr(11, 5);
let $noteLink; let $noteLink;
@@ -82,7 +82,12 @@ export async function showDialog(ancestorNoteId) {
} }
$changesList.append($('<li>') $changesList.append($('<li>')
.append(formattedTime + ' - ') .append(
$("<span>")
.text(formattedTime)
.attr("title", change.date)
)
.append(' - ')
.append($noteLink)); .append($noteLink));
} }
@@ -92,23 +97,9 @@ export async function showDialog(ancestorNoteId) {
function groupByDate(result) { function groupByDate(result) {
const groupedByDate = new Map(); const groupedByDate = new Map();
const dayCache = {};
for (const row of result) { for (const row of result) {
let dateDay = utils.parseDate(row.date); const dateDay = row.date.substr(0, 10);
dateDay.setHours(0);
dateDay.setMinutes(0);
dateDay.setSeconds(0);
dateDay.setMilliseconds(0);
// this stupidity is to make sure that we always use the same day object because Map uses only
// reference equality
if (dayCache[dateDay]) {
dateDay = dayCache[dateDay];
}
else {
dayCache[dateDay] = dateDay;
}
if (!groupedByDate.has(dateDay)) { if (!groupedByDate.has(dateDay)) {
groupedByDate.set(dateDay, []); groupedByDate.set(dateDay, []);

View File

@@ -0,0 +1,48 @@
import FlexContainer from "../widgets/flex_container.js";
import GlobalMenuWidget from "../widgets/global_menu.js";
import TabRowWidget from "../widgets/tab_row.js";
import TitleBarButtonsWidget from "../widgets/title_bar_buttons.js";
import NoteTreeWidget from "../widgets/note_tree.js";
import TabCachingWidget from "../widgets/tab_caching_widget.js";
import NoteTitleWidget from "../widgets/note_title.js";
import RunScriptButtonsWidget from "../widgets/run_script_buttons.js";
import ProtectedNoteSwitchWidget from "../widgets/protected_note_switch.js";
import NoteTypeWidget from "../widgets/note_type.js";
import NoteActionsWidget from "../widgets/note_actions.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import NoteDetailWidget from "../widgets/note_detail.js";
export default class DesktopExtraWindowLayout {
constructor(customWidgets) {
this.customWidgets = customWidgets;
}
getRootWidget(appContext) {
appContext.mainTreeWidget = new NoteTreeWidget();
return new FlexContainer('column')
.setParent(appContext)
.id('root-widget')
.css('height', '100vh')
.child(new FlexContainer('row')
.child(new GlobalMenuWidget())
.child(new TabRowWidget())
.child(new TitleBarButtonsWidget()))
.child(new FlexContainer('row')
.collapsible()
.child(new FlexContainer('column').id('center-pane').css('flex-grow', '1')
.child(new FlexContainer('row').class('title-row')
.cssBlock('.title-row > * { margin: 5px; }')
.child(new NoteTitleWidget())
.child(new RunScriptButtonsWidget().hideInZenMode())
.child(new ProtectedNoteSwitchWidget().hideInZenMode())
.child(new NoteTypeWidget().hideInZenMode())
.child(new NoteActionsWidget().hideInZenMode())
)
.child(new TabCachingWidget(() => new PromotedAttributesWidget()))
.child(new TabCachingWidget(() => new NoteDetailWidget()))
.child(...this.customWidgets.get('center-pane'))
)
);
}
}

View File

@@ -1,30 +1,30 @@
import FlexContainer from "./flex_container.js"; import FlexContainer from "../widgets/flex_container.js";
import GlobalMenuWidget from "./global_menu.js"; import GlobalMenuWidget from "../widgets/global_menu.js";
import TabRowWidget from "./tab_row.js"; import TabRowWidget from "../widgets/tab_row.js";
import TitleBarButtonsWidget from "./title_bar_buttons.js"; import TitleBarButtonsWidget from "../widgets/title_bar_buttons.js";
import StandardTopWidget from "./standard_top_widget.js"; import StandardTopWidget from "../widgets/standard_top_widget.js";
import SidePaneContainer from "./side_pane_container.js"; import SidePaneContainer from "../widgets/side_pane_container.js";
import GlobalButtonsWidget from "./global_buttons.js"; import GlobalButtonsWidget from "../widgets/global_buttons.js";
import SearchBoxWidget from "./search_box.js"; import SearchBoxWidget from "../widgets/search_box.js";
import SearchResultsWidget from "./search_results.js"; import SearchResultsWidget from "../widgets/search_results.js";
import NoteTreeWidget from "./note_tree.js"; import NoteTreeWidget from "../widgets/note_tree.js";
import TabCachingWidget from "./tab_caching_widget.js"; import TabCachingWidget from "../widgets/tab_caching_widget.js";
import NotePathsWidget from "./note_paths.js"; import NotePathsWidget from "../widgets/note_paths.js";
import NoteTitleWidget from "./note_title.js"; import NoteTitleWidget from "../widgets/note_title.js";
import RunScriptButtonsWidget from "./run_script_buttons.js"; import RunScriptButtonsWidget from "../widgets/run_script_buttons.js";
import ProtectedNoteSwitchWidget from "./protected_note_switch.js"; import ProtectedNoteSwitchWidget from "../widgets/protected_note_switch.js";
import NoteTypeWidget from "./note_type.js"; import NoteTypeWidget from "../widgets/note_type.js";
import NoteActionsWidget from "./note_actions.js"; import NoteActionsWidget from "../widgets/note_actions.js";
import PromotedAttributesWidget from "./promoted_attributes.js"; import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import NoteDetailWidget from "./note_detail.js"; import NoteDetailWidget from "../widgets/note_detail.js";
import NoteInfoWidget from "./note_info.js"; import NoteInfoWidget from "../widgets/collapsible_widgets/note_info.js";
import CalendarWidget from "./calendar.js"; import CalendarWidget from "../widgets/collapsible_widgets/calendar.js";
import AttributesWidget from "./attributes.js"; import AttributesWidget from "../widgets/collapsible_widgets/attributes.js";
import LinkMapWidget from "./link_map.js"; import LinkMapWidget from "../widgets/collapsible_widgets/link_map.js";
import NoteRevisionsWidget from "./note_revisions.js"; import NoteRevisionsWidget from "../widgets/collapsible_widgets/note_revisions.js";
import SimilarNotesWidget from "./similar_notes.js"; import SimilarNotesWidget from "../widgets/collapsible_widgets/similar_notes.js";
import WhatLinksHereWidget from "./what_links_here.js"; import WhatLinksHereWidget from "../widgets/collapsible_widgets/what_links_here.js";
import SidePaneToggles from "./side_pane_toggles.js"; import SidePaneToggles from "../widgets/side_pane_toggles.js";
import appContext from "../services/app_context.js"; import appContext from "../services/app_context.js";
const RIGHT_PANE_CSS = ` const RIGHT_PANE_CSS = `
@@ -98,7 +98,7 @@ const RIGHT_PANE_CSS = `
} }
</style>`; </style>`;
export default class DesktopLayout { export default class DesktopMainWindowLayout {
constructor(customWidgets) { constructor(customWidgets) {
this.customWidgets = customWidgets; this.customWidgets = customWidgets;
} }

View File

@@ -1,11 +1,11 @@
import FlexContainer from "./flex_container.js"; import FlexContainer from "../widgets/flex_container.js";
import NoteTitleWidget from "./note_title.js"; import NoteTitleWidget from "../widgets/note_title.js";
import NoteDetailWidget from "./note_detail.js"; import NoteDetailWidget from "../widgets/note_detail.js";
import NoteTreeWidget from "./note_tree.js"; import NoteTreeWidget from "../widgets/note_tree.js";
import MobileGlobalButtonsWidget from "./mobile_global_buttons.js"; import MobileGlobalButtonsWidget from "../widgets/mobile_widgets/mobile_global_buttons.js";
import CloseDetailButtonWidget from "./close_detail_button.js"; import CloseDetailButtonWidget from "../widgets/mobile_widgets/close_detail_button.js";
import MobileDetailMenuWidget from "./mobile_detail_menu.js"; import MobileDetailMenuWidget from "../widgets/mobile_widgets/mobile_detail_menu.js";
import ScreenContainer from "./screen_container.js"; import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
const MOBILE_CSS = ` const MOBILE_CSS = `
<style> <style>

View File

@@ -1,5 +1,5 @@
import appContext from "./services/app_context.js"; import appContext from "./services/app_context.js";
import MobileLayout from "./widgets/mobile_layout.js"; import MobileLayout from "./layouts/mobile_layout.js";
import glob from "./services/glob.js"; import glob from "./services/glob.js";
glob.setupGlobs(); glob.setupGlobs();

View File

@@ -9,10 +9,16 @@ 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";
import keyboardActionsService from "./keyboard_actions.js"; import keyboardActionsService from "./keyboard_actions.js";
import MobileScreenSwitcherExecutor from "../widgets/mobile_screen_switcher.js"; import MobileScreenSwitcherExecutor from "../widgets/mobile_widgets/mobile_screen_switcher.js";
import MainTreeExecutors from "./main_tree_executors.js"; import MainTreeExecutors from "./main_tree_executors.js";
class AppContext extends Component { class AppContext extends Component {
constructor(isMainWindow) {
super();
this.isMainWindow = isMainWindow;
}
setLayout(layout) { setLayout(layout) {
this.layout = layout; this.layout = layout;
} }
@@ -95,14 +101,21 @@ class AppContext extends Component {
return $(el).closest(".component").prop('component'); return $(el).closest(".component").prop('component');
} }
async protectedSessionStartedEvent() { async openInNewWindow(notePath) {
await treeCache.loadInitialTree(); if (utils.isElectron()) {
const {ipcRenderer} = utils.dynamicRequire('electron');
this.triggerEvent('treeCacheReloaded'); ipcRenderer.send('create-extra-window', {notePath});
}
else {
const url = window.location.protocol + '//' + window.location.host + window.location.pathname + '?extra=1#' + notePath;
window.open(url, '', 'width=1000,height=800');
}
} }
} }
const appContext = new AppContext(); const appContext = new AppContext(window.glob.isMainWindow);
// we should save all outstanding changes before the page/app is closed // we should save all outstanding changes before the page/app is closed
$(window).on('beforeunload', () => { $(window).on('beforeunload', () => {

View File

@@ -113,12 +113,16 @@ 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: "arrow-up-right"},
{title: "Open note in new window", command: "openNoteInNewWindow", uiIcon: "arrow-up-right"}
], ],
selectMenuItemHandler: ({command}) => { selectMenuItemHandler: ({command}) => {
if (command === 'openNoteInNewTab') { if (command === 'openNoteInNewTab') {
appContext.tabManager.openTabWithNote(notePath); appContext.tabManager.openTabWithNote(notePath);
} }
else if (command === 'openNoteInNewWindow') {
appContext.openInNewWindow(notePath);
}
} }
}); });
} }

View File

@@ -1,13 +1,10 @@
import treeService from './tree.js';
import utils from './utils.js'; import utils from './utils.js';
import server from './server.js'; import server from './server.js';
import protectedSessionHolder from './protected_session_holder.js'; import protectedSessionHolder from './protected_session_holder.js';
import toastService from "./toast.js"; import toastService from "./toast.js";
import ws from "./ws.js"; import ws from "./ws.js";
import appContext from "./app_context.js"; import appContext from "./app_context.js";
import treeCache from "./tree_cache.js";
const $enterProtectedSessionButton = $("#enter-protected-session-button");
const $leaveProtectedSessionButton = $("#leave-protected-session-button");
let protectedSessionDeferred = null; let protectedSessionDeferred = null;
@@ -45,6 +42,10 @@ async function setupProtectedSession(password) {
protectedSessionHolder.setProtectedSessionId(response.protectedSessionId); protectedSessionHolder.setProtectedSessionId(response.protectedSessionId);
protectedSessionHolder.touchProtectedSession(); protectedSessionHolder.touchProtectedSession();
await treeCache.loadInitialTree();
await appContext.triggerEvent('treeCacheReloaded');
appContext.triggerEvent('protectedSessionStarted'); appContext.triggerEvent('protectedSessionStarted');
if (protectedSessionDeferred !== null) { if (protectedSessionDeferred !== null) {
@@ -54,9 +55,6 @@ async function setupProtectedSession(password) {
protectedSessionDeferred = null; protectedSessionDeferred = null;
} }
$enterProtectedSessionButton.hide();
$leaveProtectedSessionButton.show();
toastService.showMessage("Protected session has been started."); toastService.showMessage("Protected session has been started.");
} }

View File

@@ -39,9 +39,16 @@ function touchProtectedSession() {
} }
} }
function touchProtectedSessionIfNecessary(note) {
if (note && note.isProtected && isProtectedSessionAvailable()) {
touchProtectedSession();
}
}
export default { export default {
setProtectedSessionId, setProtectedSessionId,
resetProtectedSession, resetProtectedSession,
isProtectedSessionAvailable, isProtectedSessionAvailable,
touchProtectedSession touchProtectedSession,
touchProtectedSessionIfNecessary
}; };

View File

@@ -8,6 +8,7 @@ function getHeaders(headers) {
// also avoiding using underscores instead of dashes since nginx filters them out by default // also avoiding using underscores instead of dashes since nginx filters them out by default
const allHeaders = { const allHeaders = {
'trilium-source-id': glob.sourceId, 'trilium-source-id': glob.sourceId,
'trilium-local-now-datetime': utils.localNowDateTime(),
'x-csrf-token': glob.csrfToken 'x-csrf-token': glob.csrfToken
}; };

View File

@@ -58,6 +58,7 @@ class TabContext extends Component {
this.autoBookDisabled = false; this.autoBookDisabled = false;
this.textPreviewDisabled = false; this.textPreviewDisabled = false;
this.codePreviewDisabled = false;
setTimeout(async () => { setTimeout(async () => {
// we include the note into recent list only if the user stayed on the note at least 5 seconds // we include the note into recent list only if the user stayed on the note at least 5 seconds
@@ -69,10 +70,7 @@ class TabContext extends Component {
} }
}, 5000); }, 5000);
if (this.note.isProtected && protectedSessionHolder.isProtectedSessionAvailable()) { protectedSessionHolder.touchProtectedSessionIfNecessary(this.note);
// FIXME: there are probably more places where this should be done
protectedSessionHolder.touchProtectedSession();
}
if (triggerSwitchEvent) { if (triggerSwitchEvent) {
this.triggerEvent('tabNoteSwitched', { this.triggerEvent('tabNoteSwitched', {

View File

@@ -6,6 +6,7 @@ import treeCache from "./tree_cache.js";
import treeService from "./tree.js"; import treeService from "./tree.js";
import utils from "./utils.js"; import utils from "./utils.js";
import TabContext from "./tab_context.js"; import TabContext from "./tab_context.js";
import appContext from "./app_context.js";
export default class TabManager extends Component { export default class TabManager extends Component {
constructor() { constructor() {
@@ -14,6 +15,10 @@ export default class TabManager extends Component {
this.activeTabId = null; this.activeTabId = null;
this.tabsUpdate = new SpacedUpdate(async () => { this.tabsUpdate = new SpacedUpdate(async () => {
if (!appContext.isMainWindow) {
return;
}
const openTabs = this.tabContexts const openTabs = this.tabContexts
.map(tc => tc.getTabState()) .map(tc => tc.getTabState())
.filter(t => !!t); .filter(t => !!t);
@@ -30,7 +35,9 @@ export default class TabManager extends Component {
} }
async loadTabs() { async loadTabs() {
const openTabs = options.getJson('openTabs') || []; const tabsToOpen = appContext.isMainWindow
? (options.getJson('openTabs') || [])
: [];
// if there's notePath in the URL, make sure it's open and active // if there's notePath in the URL, make sure it's open and active
// (useful, among others, for opening clipped notes from clipper) // (useful, among others, for opening clipped notes from clipper)
@@ -39,17 +46,17 @@ export default class TabManager extends Component {
const noteId = treeService.getNoteIdFromNotePath(notePath); const noteId = treeService.getNoteIdFromNotePath(notePath);
if (noteId && await treeCache.noteExists(noteId)) { if (noteId && await treeCache.noteExists(noteId)) {
for (const tab of openTabs) { for (const tab of tabsToOpen) {
tab.active = false; tab.active = false;
} }
const foundTab = openTabs.find(tab => noteId === treeService.getNoteIdFromNotePath(tab.notePath)); const foundTab = tabsToOpen.find(tab => noteId === treeService.getNoteIdFromNotePath(tab.notePath));
if (foundTab) { if (foundTab) {
foundTab.active = true; foundTab.active = true;
} }
else { else {
openTabs.push({ tabsToOpen.push({
notePath: notePath, notePath: notePath,
active: true active: true
}); });
@@ -59,7 +66,7 @@ export default class TabManager extends Component {
let filteredTabs = []; let filteredTabs = [];
for (const openTab of openTabs) { for (const openTab of tabsToOpen) {
const noteId = treeService.getNoteIdFromNotePath(openTab.notePath); const noteId = treeService.getNoteIdFromNotePath(openTab.notePath);
if (await treeCache.noteExists(noteId)) { if (await treeCache.noteExists(noteId)) {
@@ -315,6 +322,14 @@ export default class TabManager extends Component {
} }
} }
moveTabToNewWindowCommand({tabId}) {
const notePath = this.getTabContextById(tabId).notePath;
this.removeTab(tabId);
appContext.openInNewWindow(notePath);
}
async hoistedNoteChangedEvent({hoistedNoteId}) { async hoistedNoteChangedEvent({hoistedNoteId}) {
if (hoistedNoteId === 'root') { if (hoistedNoteId === 'root') {
return; return;

View File

@@ -98,9 +98,8 @@ async function prepareNode(branch) {
key: utils.randomString(12) // this should prevent some "duplicate key" errors key: utils.randomString(12) // this should prevent some "duplicate key" errors
}; };
if (note.hasChildren() || note.type === 'search') { node.folder = getChildBranchesWithoutImages(note).length > 0
node.folder = true; || note.type === 'search';
}
return node; return node;
} }
@@ -108,16 +107,9 @@ async function prepareNode(branch) {
async function prepareRealBranch(parentNote) { async function prepareRealBranch(parentNote) {
utils.assertArguments(parentNote); utils.assertArguments(parentNote);
const childBranches = await parentNote.getChildBranches();
if (!childBranches) {
ws.logError(`No children for ${parentNote}. This shouldn't happen.`);
return;
}
const noteList = []; const noteList = [];
for (const branch of childBranches) { for (const branch of getChildBranchesWithoutImages(parentNote)) {
const node = await prepareNode(branch); const node = await prepareNode(branch);
noteList.push(node); noteList.push(node);
@@ -126,6 +118,20 @@ async function prepareRealBranch(parentNote) {
return noteList; return noteList;
} }
function getChildBranchesWithoutImages(parentNote) {
const childBranches = parentNote.getChildBranches();
if (!childBranches) {
ws.logError(`No children for ${parentNote}. This shouldn't happen.`);
return;
}
const imageLinks = parentNote.getRelations('imageLink');
// image is already visible in the parent note so no need to display it separately in the book
return childBranches.filter(branch => !imageLinks.find(rel => rel.value === branch.noteId));
}
async function prepareSearchBranch(note) { async function prepareSearchBranch(note) {
await treeCache.reloadNotes([note.noteId]); await treeCache.reloadNotes([note.noteId]);
@@ -170,5 +176,6 @@ export default {
prepareRootNode, prepareRootNode,
prepareBranch, prepareBranch,
getExtraClasses, getExtraClasses,
getIcon getIcon,
getChildBranchesWithoutImages
} }

View File

@@ -56,7 +56,8 @@ class TreeContextMenu {
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch; const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
return [ return [
{ title: 'Open in new tab', command: "openInTab", uiIcon: "empty", enabled: noSelectedNotes }, { title: 'Open in a new tab <kbd>Ctrl+Click</kbd>', command: "openInTab", uiIcon: "empty", enabled: noSelectedNotes },
{ title: 'Open in a new window', command: "openInWindow", uiIcon: "empty", enabled: noSelectedNotes },
{ title: 'Insert note after <kbd data-command="createNoteAfter"></kbd>', command: "insertNoteAfter", uiIcon: "plus", { title: 'Insert note after <kbd data-command="createNoteAfter"></kbd>', command: "insertNoteAfter", uiIcon: "plus",
items: insertNoteAfterEnabled ? this.getNoteTypeItems("insertNoteAfter") : null, items: insertNoteAfterEnabled ? this.getNoteTypeItems("insertNoteAfter") : null,
enabled: insertNoteAfterEnabled && noSelectedNotes }, enabled: insertNoteAfterEnabled && noSelectedNotes },
@@ -111,6 +112,9 @@ class TreeContextMenu {
if (command === 'openInTab') { if (command === 'openInTab') {
appContext.tabManager.openTabWithNote(notePath); appContext.tabManager.openTabWithNote(notePath);
} }
else if (command === 'openInWindow') {
appContext.openInNewWindow(notePath);
}
else if (command === "insertNoteAfter") { else if (command === "insertNoteAfter") {
const parentNoteId = this.node.data.parentNoteId; const parentNoteId = this.node.data.parentNoteId;
const isProtected = await treeService.getParentProtectedStatus(this.node); const isProtected = await treeService.getParentProtectedStatus(this.node);

View File

@@ -40,6 +40,10 @@ function formatDateTime(date) {
return formatDate(date) + " " + formatTime(date); return formatDate(date) + " " + formatTime(date);
} }
function localNowDateTime() {
return dayjs().format('YYYY-MM-DD HH:mm:ss.SSSZZ')
}
function now() { function now() {
return formatTimeWithSeconds(new Date()); return formatTimeWithSeconds(new Date());
} }
@@ -321,6 +325,7 @@ export default {
formatDate, formatDate,
formatDateISO, formatDateISO,
formatDateTime, formatDateTime,
localNowDateTime,
now, now,
isElectron, isElectron,
isMac, isMac,

View File

@@ -57,7 +57,10 @@ class BasicWidget extends Component {
for (const key in this.attrs) { for (const key in this.attrs) {
if (key === 'style') { if (key === 'style') {
if (this.attrs[key]) { if (this.attrs[key]) {
$widget.attr(key, $widget.attr('style') + ';' + this.attrs[key]); let style = $widget.attr('style');
style = style ? `${style}; ${this.attrs[key]}` : this.attrs[key];
$widget.attr(key, style);
} }
} }
else { else {

View File

@@ -1,7 +1,7 @@
import utils from "../services/utils.js"; import utils from "../../services/utils.js";
import linkService from "../services/link.js"; import linkService from "../../services/link.js";
import ws from "../services/ws.js"; import ws from "../../services/ws.js";
import CollapsibleWidget from "./collapsible_widget.js"; import CollapsibleWidget from "../collapsible_widget.js";
export default class AttributesWidget extends CollapsibleWidget { export default class AttributesWidget extends CollapsibleWidget {
get widgetTitle() { return "Attributes"; } get widgetTitle() { return "Attributes"; }
@@ -16,7 +16,7 @@ export default class AttributesWidget extends CollapsibleWidget {
get headerActions() { get headerActions() {
const $showFullButton = $("<a>").append("show dialog").addClass('widget-header-action'); const $showFullButton = $("<a>").append("show dialog").addClass('widget-header-action');
$showFullButton.on('click', async () => { $showFullButton.on('click', async () => {
const attributesDialog = await import("../dialogs/attributes.js"); const attributesDialog = await import("../../dialogs/attributes.js");
attributesDialog.showDialog(); attributesDialog.showDialog();
}); });

View File

@@ -1,9 +1,9 @@
import CollapsibleWidget from "./collapsible_widget.js"; import CollapsibleWidget from "../collapsible_widget.js";
import libraryLoader from "../services/library_loader.js"; import libraryLoader from "../../services/library_loader.js";
import utils from "../services/utils.js"; import utils from "../../services/utils.js";
import dateNoteService from "../services/date_notes.js"; import dateNoteService from "../../services/date_notes.js";
import server from "../services/server.js"; import server from "../../services/server.js";
import appContext from "../services/app_context.js"; import appContext from "../../services/app_context.js";
const TPL = ` const TPL = `
<div class="calendar-widget"> <div class="calendar-widget">

View File

@@ -1,7 +1,7 @@
import CollapsibleWidget from "./collapsible_widget.js"; import CollapsibleWidget from "../collapsible_widget.js";
import linkService from "../services/link.js"; import linkService from "../../services/link.js";
import server from "../services/server.js"; import server from "../../services/server.js";
import treeCache from "../services/tree_cache.js"; import treeCache from "../../services/tree_cache.js";
export default class EditedNotesWidget extends CollapsibleWidget { export default class EditedNotesWidget extends CollapsibleWidget {
get widgetTitle() { return "Edited notes on this day"; } get widgetTitle() { return "Edited notes on this day"; }

View File

@@ -1,4 +1,4 @@
import CollapsibleWidget from "./collapsible_widget.js"; import CollapsibleWidget from "../collapsible_widget.js";
let linkMapContainerIdCtr = 1; let linkMapContainerIdCtr = 1;
@@ -21,7 +21,7 @@ export default class LinkMapWidget extends CollapsibleWidget {
get headerActions() { get headerActions() {
const $showFullButton = $("<a>").append("show full").addClass('widget-header-action'); const $showFullButton = $("<a>").append("show full").addClass('widget-header-action');
$showFullButton.on('click', async () => { $showFullButton.on('click', async () => {
const linkMapDialog = await import("../dialogs/link_map.js"); const linkMapDialog = await import("../../dialogs/link_map.js");
linkMapDialog.showDialog(); linkMapDialog.showDialog();
}); });
@@ -66,7 +66,7 @@ export default class LinkMapWidget extends CollapsibleWidget {
const $linkMapContainer = this.$body.find('.link-map-container'); const $linkMapContainer = this.$body.find('.link-map-container');
$linkMapContainer.attr("id", "link-map-container-" + linkMapContainerIdCtr++); $linkMapContainer.attr("id", "link-map-container-" + linkMapContainerIdCtr++);
const LinkMapServiceClass = (await import('../services/link_map.js')).default; const LinkMapServiceClass = (await import('../../services/link_map.js')).default;
this.linkMapService = new LinkMapServiceClass(note, $linkMapContainer, { this.linkMapService = new LinkMapServiceClass(note, $linkMapContainer, {
maxDepth: 1, maxDepth: 1,

View File

@@ -1,4 +1,4 @@
import CollapsibleWidget from "./collapsible_widget.js"; import CollapsibleWidget from "../collapsible_widget.js";
const TPL = ` const TPL = `
<table class="note-info-widget-table"> <table class="note-info-widget-table">

View File

@@ -1,5 +1,5 @@
import server from "../services/server.js"; import server from "../../services/server.js";
import CollapsibleWidget from "./collapsible_widget.js"; import CollapsibleWidget from "../collapsible_widget.js";
const TPL = ` const TPL = `
<ul class="note-revision-list" style="max-height: 150px; overflow: auto;"> <ul class="note-revision-list" style="max-height: 150px; overflow: auto;">
@@ -19,7 +19,7 @@ class NoteRevisionsWidget extends CollapsibleWidget {
get headerActions() { get headerActions() {
const $showFullButton = $("<a>").append("show dialog").addClass('widget-header-action'); const $showFullButton = $("<a>").append("show dialog").addClass('widget-header-action');
$showFullButton.on('click', async () => { $showFullButton.on('click', async () => {
const attributesDialog = await import("../dialogs/note_revisions.js"); const attributesDialog = await import("../../dialogs/note_revisions.js");
attributesDialog.showCurrentNoteRevisions(this.noteId); attributesDialog.showCurrentNoteRevisions(this.noteId);
}); });

View File

@@ -1,7 +1,7 @@
import CollapsibleWidget from "./collapsible_widget.js"; import CollapsibleWidget from "../collapsible_widget.js";
import linkService from "../services/link.js"; import linkService from "../../services/link.js";
import server from "../services/server.js"; import server from "../../services/server.js";
import treeCache from "../services/tree_cache.js"; import treeCache from "../../services/tree_cache.js";
export default class SimilarNotesWidget extends CollapsibleWidget { export default class SimilarNotesWidget extends CollapsibleWidget {
get widgetTitle() { return "Similar notes"; } get widgetTitle() { return "Similar notes"; }

View File

@@ -1,5 +1,5 @@
import CollapsibleWidget from "./collapsible_widget.js"; import CollapsibleWidget from "../collapsible_widget.js";
import linkService from "../services/link.js"; import linkService from "../../services/link.js";
export default class WhatLinksHereWidget extends CollapsibleWidget { export default class WhatLinksHereWidget extends CollapsibleWidget {
get widgetTitle() { return "What links here"; } get widgetTitle() { return "What links here"; }
@@ -13,7 +13,7 @@ export default class WhatLinksHereWidget extends CollapsibleWidget {
get headerActions() { get headerActions() {
const $showFullButton = $("<a>").append("show link map").addClass('widget-header-action'); const $showFullButton = $("<a>").append("show link map").addClass('widget-header-action');
$showFullButton.on('click', async () => { $showFullButton.on('click', async () => {
const linkMapDialog = await import("../dialogs/link_map.js"); const linkMapDialog = await import("../../dialogs/link_map.js");
linkMapDialog.showDialog(); linkMapDialog.showDialog();
}); });

View File

@@ -1,4 +1,4 @@
import BasicWidget from "./basic_widget.js"; import BasicWidget from "../basic_widget.js";
const TPL = ` const TPL = `
<button type="button" class="action-button d-sm-none d-md-none d-lg-none d-xl-none" aria-label="Close"> <button type="button" class="action-button d-sm-none d-md-none d-lg-none d-xl-none" aria-label="Close">

View File

@@ -1,8 +1,8 @@
import BasicWidget from "./basic_widget.js"; import BasicWidget from "../basic_widget.js";
import appContext from "../services/app_context.js"; import appContext from "../../services/app_context.js";
import contextMenu from "../services/context_menu.js"; import contextMenu from "../../services/context_menu.js";
import noteCreateService from "../services/note_create.js"; import noteCreateService from "../../services/note_create.js";
import branchService from "../services/branches.js"; import branchService from "../../services/branches.js";
const TPL = `<button type="button" class="action-button bx bx-menu"></button>`; const TPL = `<button type="button" class="action-button bx bx-menu"></button>`;

View File

@@ -1,4 +1,4 @@
import BasicWidget from "./basic_widget.js"; import BasicWidget from "../basic_widget.js";
const WIDGET_TPL = ` const WIDGET_TPL = `
<div id="global-buttons"> <div id="global-buttons">

View File

@@ -1,4 +1,4 @@
import Component from "./component.js"; import Component from "../component.js";
export default class MobileScreenSwitcherExecutor extends Component { export default class MobileScreenSwitcherExecutor extends Component {
setActiveScreenCommand({screen}) { setActiveScreenCommand({screen}) {

View File

@@ -1,4 +1,4 @@
import FlexContainer from "./flex_container.js"; import FlexContainer from "../flex_container.js";
export default class ScreenContainer extends FlexContainer { export default class ScreenContainer extends FlexContainer {
constructor(screenName, direction) { constructor(screenName, direction) {

View File

@@ -6,7 +6,7 @@ import server from "../services/server.js";
import libraryLoader from "../services/library_loader.js"; import libraryLoader from "../services/library_loader.js";
import EmptyTypeWidget from "./type_widgets/empty.js"; import EmptyTypeWidget from "./type_widgets/empty.js";
import EditableTextTypeWidget from "./type_widgets/editable_text.js"; import EditableTextTypeWidget from "./type_widgets/editable_text.js";
import CodeTypeWidget from "./type_widgets/code.js"; import EditableCodeTypeWidget from "./type_widgets/editable_code.js";
import FileTypeWidget from "./type_widgets/file.js"; import FileTypeWidget from "./type_widgets/file.js";
import ImageTypeWidget from "./type_widgets/image.js"; import ImageTypeWidget from "./type_widgets/image.js";
import SearchTypeWidget from "./type_widgets/search.js"; import SearchTypeWidget from "./type_widgets/search.js";
@@ -19,6 +19,7 @@ import keyboardActionsService from "../services/keyboard_actions.js";
import noteCreateService from "../services/note_create.js"; import noteCreateService from "../services/note_create.js";
import DeletedTypeWidget from "./type_widgets/deleted.js"; import DeletedTypeWidget from "./type_widgets/deleted.js";
import ReadOnlyTextTypeWidget from "./type_widgets/read_only_text.js"; import ReadOnlyTextTypeWidget from "./type_widgets/read_only_text.js";
import ReadOnlyCodeTypeWidget from "./type_widgets/read_only_code.js";
const TPL = ` const TPL = `
<div class="note-detail"> <div class="note-detail">
@@ -38,7 +39,8 @@ const typeWidgetClasses = {
'deleted': DeletedTypeWidget, 'deleted': DeletedTypeWidget,
'editable-text': EditableTextTypeWidget, 'editable-text': EditableTextTypeWidget,
'read-only-text': ReadOnlyTextTypeWidget, 'read-only-text': ReadOnlyTextTypeWidget,
'code': CodeTypeWidget, 'editable-code': EditableCodeTypeWidget,
'read-only-code': ReadOnlyCodeTypeWidget,
'file': FileTypeWidget, 'file': FileTypeWidget,
'image': ImageTypeWidget, 'image': ImageTypeWidget,
'search': SearchTypeWidget, 'search': SearchTypeWidget,
@@ -61,6 +63,8 @@ export default class NoteDetailWidget extends TabAwareWidget {
const dto = note.dto; const dto = note.dto;
dto.content = this.getTypeWidget().getContent(); dto.content = this.getTypeWidget().getContent();
protectedSessionHolder.touchProtectedSessionIfNecessary(note);
await server.put('notes/' + noteId, dto, this.componentId); await server.put('notes/' + noteId, dto, this.componentId);
}); });
} }
@@ -187,10 +191,23 @@ export default class NoteDetailWidget extends TabAwareWidget {
} }
} }
if (type === 'code' && !this.tabContext.codePreviewDisabled) {
const noteComplement = await this.tabContext.getNoteComplement();
if (note.hasLabel('readOnly') ||
(noteComplement.content && noteComplement.content.length > 30000)) {
type = 'read-only-code';
}
}
if (type === 'text') { if (type === 'text') {
type = 'editable-text'; type = 'editable-text';
} }
if (type === 'code') {
type = 'editable-code';
}
if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) { if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) {
type = 'protected-session'; type = 'protected-session';
} }
@@ -274,6 +291,12 @@ export default class NoteDetailWidget extends TabAwareWidget {
} }
} }
codePreviewDisabledEvent({tabContext}) {
if (this.isTab(tabContext.tabId)) {
this.refresh();
}
}
async cutIntoNoteCommand() { async cutIntoNoteCommand() {
const note = appContext.tabManager.getActiveTabNote(); const note = appContext.tabManager.getActiveTabNote();

View File

@@ -29,6 +29,8 @@ export default class NoteTitleWidget extends TabAwareWidget {
this.spacedUpdate = new SpacedUpdate(async () => { this.spacedUpdate = new SpacedUpdate(async () => {
const title = this.$noteTitle.val(); const title = this.$noteTitle.val();
protectedSessionHolder.touchProtectedSessionIfNecessary(this.note);
await server.put(`notes/${this.noteId}/change-title`, {title}); await server.put(`notes/${this.noteId}/change-title`, {title});
}); });
} }

View File

@@ -389,7 +389,8 @@ export default class NoteTreeWidget extends TabAwareWidget {
node.data.isProtected = note.isProtected; node.data.isProtected = note.isProtected;
node.data.noteType = note.type; node.data.noteType = note.type;
node.folder = note.type === 'search' || note.getChildNoteIds().length > 0; node.folder = treeBuilder.getChildBranchesWithoutImages(note).length > 0
|| note.type === 'search';
node.icon = treeBuilder.getIcon(note); node.icon = treeBuilder.getIcon(note);
node.extraClasses = treeBuilder.getExtraClasses(note); node.extraClasses = treeBuilder.getExtraClasses(note);
node.title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title; node.title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title;
@@ -481,6 +482,15 @@ export default class NoteTreeWidget extends TabAwareWidget {
// missing handling of things inherited from template // missing handling of things inherited from template
noteIdsToReload.add(attr.noteId); noteIdsToReload.add(attr.noteId);
} }
else if (attr.type === 'relation' && attr.name === 'imageLink') {
const note = treeCache.getNoteFromCache(attr.noteId);
if (note && note.getChildNoteIds().includes(attr.value)) {
// there's new/deleted imageLink betwen note and its image child - which can show/hide
// the image (if there is a imageLink relation between parent and child then it is assumed to be "contained" in the note and thus does not have to be displayed in the tree)
noteIdsToReload.add(attr.noteId);
}
}
} }
for (const branch of loadResults.getBranches()) { for (const branch of loadResults.getBranches()) {

View File

@@ -5,12 +5,12 @@ const TPL = `
<div class="btn-group btn-group-xs"> <div class="btn-group btn-group-xs">
<button type="button" <button type="button"
class="btn btn-sm icon-button bx bx-check-shield protect-button" class="btn btn-sm icon-button bx bx-check-shield protect-button"
title="Protected note can be viewed and edited only after entering password"> title="Set this note as protected which means it will possible to view and edit this note only after entering password">
</button> </button>
<button type="button" <button type="button"
class="btn btn-sm icon-button bx bx-shield-x unprotect-button" class="btn btn-sm icon-button bx bx-shield-x unprotect-button"
title="Not protected note can be viewed without entering password"> title="Set this note as unprotected which will make it viewable and editable without entering password">
</button> </button>
</div>`;``; </div>`;``;

View File

@@ -57,29 +57,39 @@ export default class TabCachingWidget extends TabAwareWidget {
} }
} }
/**
* widget.hasBeenAlreadyShown is intended for lazy loading of cached tabs - initial note switches of new tabs
* are not executed, we're waiting for the first tab activation and then we update the tab. After this initial
* activation further note switches are always propagated to the tabs.
*/
handleEventInChildren(name, data) { handleEventInChildren(name, data) {
// stop propagation of the event to the children, individual tab widget should not know about tab switching if (['tabNoteSwitched', 'tabNoteSwitchedAndActivated'].includes(name)) {
// since they are per-tab
if (name === 'tabNoteSwitchedAndActivated') {
name = 'tabNoteSwitched';
}
if (name === 'tabNoteSwitched') {
// this event is propagated only to the widgets of a particular tab // this event is propagated only to the widgets of a particular tab
const widget = this.widgets[data.tabContext.tabId]; const widget = this.widgets[data.tabContext.tabId];
if (widget) { if (widget && (widget.hasBeenAlreadyShown || name === 'tabNoteSwitchedAndActivated')) {
return widget.handleEvent(name, data); widget.hasBeenAlreadyShown = true;
return widget.handleEvent('tabNoteSwitched', data);
} }
else { else {
return Promise.resolve(); return Promise.resolve();
} }
} }
if (name !== 'activeTabChanged') { if (name === 'activeTabChanged') {
const widget = this.widgets[data.tabContext.tabId];
if (widget.hasBeenAlreadyShown) {
return Promise.resolve();
}
else {
widget.hasBeenAlreadyShown = true;
return widget.handleEvent(name, data);
}
} else {
return super.handleEventInChildren(name, data); return super.handleEventInChildren(name, data);
} }
return Promise.resolve();
} }
} }

View File

@@ -258,8 +258,9 @@ export default class TabRowWidget extends BasicWidget {
x: e.pageX, x: e.pageX,
y: e.pageY, y: e.pageY,
items: [ items: [
{title: "Move this tab to a new window", command: "moveTabToNewWindow", uiIcon: "empty"},
{title: "Close all tabs", command: "removeAllTabs", uiIcon: "empty"}, {title: "Close all tabs", command: "removeAllTabs", uiIcon: "empty"},
{title: "Close all tabs except for this", command: "removeAllTabsExceptForThis", uiIcon: "empty"} {title: "Close all tabs except for this", command: "removeAllTabsExceptForThis", uiIcon: "empty"},
], ],
selectMenuItemHandler: ({command}) => { selectMenuItemHandler: ({command}) => {
this.triggerCommand(command, {tabId}); this.triggerCommand(command, {tabId});

View File

@@ -21,8 +21,8 @@ const TPL = `
<div class="note-detail-code-editor"></div> <div class="note-detail-code-editor"></div>
</div>`; </div>`;
export default class CodeTypeWidget extends TypeWidget { export default class EditableCodeTypeWidget extends TypeWidget {
static getType() { return "code"; } static getType() { return "editable-code"; }
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);

View File

@@ -1,13 +1,9 @@
import libraryLoader from "../../services/library_loader.js"; import libraryLoader from "../../services/library_loader.js";
import noteAutocompleteService from '../../services/note_autocomplete.js'; import noteAutocompleteService from '../../services/note_autocomplete.js';
import mimeTypesService from '../../services/mime_types.js'; import mimeTypesService from '../../services/mime_types.js';
import TypeWidget from "./type_widget.js";
import utils from "../../services/utils.js"; import utils from "../../services/utils.js";
import appContext from "../../services/app_context.js";
import keyboardActionService from "../../services/keyboard_actions.js"; import keyboardActionService from "../../services/keyboard_actions.js";
import treeCache from "../../services/tree_cache.js"; import treeCache from "../../services/tree_cache.js";
import linkService from "../../services/link.js";
import noteContentRenderer from "../../services/note_content_renderer.js";
import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; import AbstractTextTypeWidget from "./abstract_text_type_widget.js";
const ENABLE_INSPECTOR = false; const ENABLE_INSPECTOR = false;
@@ -60,6 +56,7 @@ const TPL = `
overflow: auto; overflow: auto;
height: 100%; height: 100%;
font-family: var(--detail-text-font-family); font-family: var(--detail-text-font-family);
padding-left: 12px;
} }
.note-detail-text-editor { .note-detail-text-editor {

View File

@@ -0,0 +1,44 @@
import TypeWidget from "./type_widget.js";
const TPL = `
<div class="note-detail-read-only-code note-detail-printable">
<style>
.note-detail-read-only-code {
overflow: auto;
height: 100%;
}
.note-detail-read-only-code-content {
padding: 10px;
}
</style>
<div class="alert alert-warning no-print" style="margin-bottom: 0;">
Read only code view is shown. <a href="#" class="edit-note">Click here</a> to edit the note.
</div>
<pre class="note-detail-read-only-code-content"></pre>
</div>`;
export default class ReadOnlyCodeTypeWidget extends TypeWidget {
static getType() { return "read-only-code"; }
doRender() {
this.$widget = $(TPL);
this.$content = this.$widget.find('.note-detail-read-only-code-content');
this.$widget.find('a.edit-note').on('click', () => {
this.tabContext.codePreviewDisabled = true;
this.triggerEvent('codePreviewDisabled', {tabContext: this.tabContext});
});
return this.$widget;
}
async doRefresh(note) {
const noteComplement = await this.tabContext.getNoteComplement();
this.$content.text(noteComplement.content);
}
}

View File

@@ -48,4 +48,10 @@ export default class TypeWidget extends TabAwareWidget {
this.refresh(); this.refresh();
} }
} }
codePreviewDisabledEvent({tabContext}) {
if (this.isTab(tabContext.tabId)) {
this.refresh();
}
}
} }

View File

@@ -27,7 +27,7 @@ async function loginSync(req) {
// login token is valid for 5 minutes // login token is valid for 5 minutes
if (Math.abs(timestamp.getTime() - now.getTime()) > 5 * 60 * 1000) { if (Math.abs(timestamp.getTime() - now.getTime()) > 5 * 60 * 1000) {
return [400, { message: 'Auth request time is out of sync' }]; return [400, { message: 'Auth request time is out of sync, please check that both client and server have correct time.' }];
} }
const syncVersion = req.body.syncVersion; const syncVersion = req.body.syncVersion;

View File

@@ -18,13 +18,11 @@ async function getNoteRevision(req) {
if (noteRevision.type === 'file') { if (noteRevision.type === 'file') {
if (noteRevision.isStringNote()) { if (noteRevision.isStringNote()) {
await noteRevision.getContent(); noteRevision.content = (await noteRevision.getContent()).substr(0, 10000);
noteRevision.content = noteRevision.content.substr(0, 10000);
} }
} }
else { else {
await noteRevision.getContent(); noteRevision.content = await noteRevision.getContent();
if (noteRevision.content && noteRevision.type === 'image') { if (noteRevision.content && noteRevision.type === 'image') {
noteRevision.content = noteRevision.content.toString('base64'); noteRevision.content = noteRevision.content.toString('base64');

View File

@@ -13,17 +13,17 @@ async function getRecentChanges(req) {
SELECT * FROM ( SELECT * FROM (
SELECT note_revisions.noteId, SELECT note_revisions.noteId,
note_revisions.noteRevisionId, note_revisions.noteRevisionId,
note_revisions.utcDateCreated AS date note_revisions.dateLastEdited AS date
FROM note_revisions FROM note_revisions
ORDER BY note_revisions.utcDateCreated DESC ORDER BY note_revisions.dateLastEdited DESC
) )
UNION ALL SELECT * FROM ( UNION ALL SELECT * FROM (
SELECT SELECT
notes.noteId, notes.noteId,
NULL AS noteRevisionId, NULL AS noteRevisionId,
utcDateModified AS date dateModified AS date
FROM notes FROM notes
ORDER BY utcDateModified DESC ORDER BY dateModified DESC
) )
ORDER BY date DESC`); ORDER BY date DESC`);
@@ -44,7 +44,7 @@ async function getRecentChanges(req) {
notes.title AS current_title, notes.title AS current_title,
notes.isProtected AS current_isProtected, notes.isProtected AS current_isProtected,
note_revisions.title, note_revisions.title,
note_revisions.utcDateCreated AS date note_revisions.dateCreated AS date
FROM FROM
note_revisions note_revisions
JOIN notes USING(noteId) JOIN notes USING(noteId)
@@ -60,7 +60,7 @@ async function getRecentChanges(req) {
notes.title AS current_title, notes.title AS current_title,
notes.isProtected AS current_isProtected, notes.isProtected AS current_isProtected,
notes.title, notes.title,
notes.utcDateModified AS date notes.dateModified AS date
FROM FROM
notes notes
WHERE noteId = ?`, [noteRow.noteId])); WHERE noteId = ?`, [noteRow.noteId]));

View File

@@ -11,7 +11,7 @@ const env = require('../services/env');
async function index(req, res) { async function index(req, res) {
const options = await optionService.getOptionsMap(); const options = await optionService.getOptionsMap();
const view = req.cookies['trilium-device'] === 'mobile' ? 'mobile' : 'desktop'; let view = req.cookies['trilium-device'] === 'mobile' ? 'mobile' : 'desktop';
const csrfToken = req.csrfToken(); const csrfToken = req.csrfToken();
log.info(`Generated CSRF token ${csrfToken} with secret ${res.getHeader('set-cookie')}`); log.info(`Generated CSRF token ${csrfToken} with secret ${res.getHeader('set-cookie')}`);
@@ -26,7 +26,8 @@ async function index(req, res) {
maxSyncIdAtLoad: await sql.getValue("SELECT MAX(id) FROM sync"), maxSyncIdAtLoad: await sql.getValue("SELECT MAX(id) FROM sync"),
instanceName: config.General ? config.General.instanceName : null, instanceName: config.General ? config.General.instanceName : null,
appCssNoteIds: await getAppCssNoteIds(), appCssNoteIds: await getAppCssNoteIds(),
isDev: env.isDev() isDev: env.isDev(),
isMainWindow: !req.query.extra
}); });
} }

View File

@@ -1,3 +1,5 @@
"use strict";
const setupRoute = require('./setup'); const setupRoute = require('./setup');
const loginRoute = require('./login'); const loginRoute = require('./login');
const indexRoute = require('./index'); const indexRoute = require('./index');
@@ -82,6 +84,7 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio
try { try {
const result = await cls.init(async () => { const result = await cls.init(async () => {
cls.namespace.set('sourceId', req.headers['trilium-source-id']); cls.namespace.set('sourceId', req.headers['trilium-source-id']);
cls.namespace.set('localNowDateTime', req.headers['`trilium-local-now-datetime`']);
protectedSessionService.setProtectedSessionId(req); protectedSessionService.setProtectedSessionId(req);
if (transactional) { if (transactional) {

View File

@@ -3,10 +3,11 @@
const sqlInit = require('../services/sql_init'); const sqlInit = require('../services/sql_init');
const setupService = require('../services/setup'); const setupService = require('../services/setup');
const utils = require('../services/utils'); const utils = require('../services/utils');
const windowService = require('../services/window');
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()) {
await windowService.createMainWindow(); await windowService.createMainWindow();
windowService.closeSetupWindow(); windowService.closeSetupWindow();

View File

@@ -1 +1 @@
module.exports = { buildDate:"2020-04-20T22:39:23+02:00", buildRevision: "9f1b3cc892bdbbb150d54bac1bbe6356168ed5d3" }; module.exports = { buildDate:"2020-04-20T22:40:02+02:00", buildRevision: "a86177bb597c752fbc96a24d4be7ab5ae6c0344d" };

View File

@@ -12,7 +12,9 @@ const VIRTUAL_ATTRIBUTES = [
"type", "type",
"mime", "mime",
"text", "text",
"parentCount" "parentCount",
"attributeName",
"attributeValue"
]; ];
module.exports = function(filters, selectedColumns = 'notes.*') { module.exports = function(filters, selectedColumns = 'notes.*') {
@@ -33,11 +35,29 @@ module.exports = function(filters, selectedColumns = 'notes.*') {
// forcing to use particular index since SQLite query planner would often choose something pretty bad // forcing to use particular index since SQLite query planner would often choose something pretty bad
joins[alias] = `LEFT JOIN attributes AS ${alias} INDEXED BY IDX_attributes_noteId_index ` joins[alias] = `LEFT JOIN attributes AS ${alias} INDEXED BY IDX_attributes_noteId_index `
+ `ON ${alias}.noteId = notes.noteId ` + `ON ${alias}.noteId = notes.noteId AND ${alias}.isDeleted = 0 `
+ `AND ${alias}.name = '${property}' AND ${alias}.isDeleted = 0`; + `AND ${alias}.name = '${property}' `;
accessor = `${alias}.value`; accessor = `${alias}.value`;
} }
else if (['attributeType', 'attributeName', 'attributeValue'].includes(property)) {
const alias = "attr_filter";
if (!(alias in joins)) {
joins[alias] = `LEFT JOIN attributes AS ${alias} INDEXED BY IDX_attributes_noteId_index `
+ `ON ${alias}.noteId = notes.noteId AND ${alias}.isDeleted = 0`;
}
if (property === 'attributeType') {
accessor = `${alias}.type`
} else if (property === 'attributeName') {
accessor = `${alias}.name`
} else if (property === 'attributeValue') {
accessor = `${alias}.value`
} else {
throw new Error(`Unrecognized property ${property}`);
}
}
else if (property === 'content') { else if (property === 'content') {
const alias = "note_contents"; const alias = "note_contents";
@@ -73,79 +93,85 @@ module.exports = function(filters, selectedColumns = 'notes.*') {
}); });
} }
let where = '1';
const params = []; const params = [];
for (const filter of filters) { function parseWhereFilters(filters) {
if (['isarchived', 'in', 'orderby', 'limit'].includes(filter.name.toLowerCase())) { let whereStmt = '';
continue; // these are not real filters
}
where += " " + filter.relation + " "; for (const filter of filters) {
if (['isarchived', 'in', 'orderby', 'limit'].includes(filter.name.toLowerCase())) {
continue; // these are not real filters
}
const accessor = getAccessor(filter.name); if (whereStmt) {
whereStmt += " " + filter.relation + " ";
}
if (filter.operator === 'exists') { if (filter.children) {
where += `${accessor} IS NOT NULL`; whereStmt += "(" + parseWhereFilters(filter.children) + ")";
} continue;
else if (filter.operator === 'not-exists') { }
where += `${accessor} IS NULL`;
} const accessor = getAccessor(filter.name);
else if (filter.operator === '=' || filter.operator === '!=') {
where += `${accessor} ${filter.operator} ?`; if (filter.operator === 'exists') {
params.push(filter.value); whereStmt += `${accessor} IS NOT NULL`;
} } else if (filter.operator === 'not-exists') {
else if (filter.operator === '*=' || filter.operator === '!*=') { whereStmt += `${accessor} IS NULL`;
where += `${accessor}` } else if (filter.operator === '=' || filter.operator === '!=') {
whereStmt += `${accessor} ${filter.operator} ?`;
params.push(filter.value);
} else if (filter.operator === '*=' || filter.operator === '!*=') {
whereStmt += `${accessor}`
+ (filter.operator.includes('!') ? ' NOT' : '') + (filter.operator.includes('!') ? ' NOT' : '')
+ ` LIKE ` + utils.prepareSqlForLike('%', filter.value, ''); + ` LIKE ` + utils.prepareSqlForLike('%', filter.value, '');
} } else if (filter.operator === '=*' || filter.operator === '!=*') {
else if (filter.operator === '=*' || filter.operator === '!=*') { whereStmt += `${accessor}`
where += `${accessor}`
+ (filter.operator.includes('!') ? ' NOT' : '') + (filter.operator.includes('!') ? ' NOT' : '')
+ ` LIKE ` + utils.prepareSqlForLike('', filter.value, '%'); + ` LIKE ` + utils.prepareSqlForLike('', filter.value, '%');
} } else if (filter.operator === '*=*' || filter.operator === '!*=*') {
else if (filter.operator === '*=*' || filter.operator === '!*=*') { const columns = filter.name === 'text' ? [getAccessor("title"), getAccessor("content")] : [accessor];
const columns = filter.name === 'text' ? [getAccessor("title"), getAccessor("content")] : [accessor];
let condition = "(" + columns.map(column => let condition = "(" + columns.map(column =>
`${column}` + ` LIKE ` + utils.prepareSqlForLike('%', filter.value, '%')) `${column}` + ` LIKE ` + utils.prepareSqlForLike('%', filter.value, '%'))
.join(" OR ") + ")"; .join(" OR ") + ")";
if (filter.operator.includes('!')) { if (filter.operator.includes('!')) {
condition = "NOT(" + condition + ")"; condition = "NOT(" + condition + ")";
} }
if (['text', 'title', 'content'].includes(filter.name)) { if (['text', 'title', 'content'].includes(filter.name)) {
// for title/content search does not make sense to search for protected notes // for title/content search does not make sense to search for protected notes
condition = `(${condition} AND notes.isProtected = 0)`; condition = `(${condition} AND notes.isProtected = 0)`;
} }
where += condition; whereStmt += condition;
} } else if ([">", ">=", "<", "<="].includes(filter.operator)) {
else if ([">", ">=", "<", "<="].includes(filter.operator)) { let floatParam;
let floatParam;
// from https://stackoverflow.com/questions/12643009/regular-expression-for-floating-point-numbers // from https://stackoverflow.com/questions/12643009/regular-expression-for-floating-point-numbers
if (/^[+-]?([0-9]*[.])?[0-9]+$/.test(filter.value)) { if (/^[+-]?([0-9]*[.])?[0-9]+$/.test(filter.value)) {
floatParam = parseFloat(filter.value); floatParam = parseFloat(filter.value);
} }
if (floatParam === undefined || isNaN(floatParam)) { if (floatParam === undefined || isNaN(floatParam)) {
// if the value can't be parsed as float then we assume that string comparison should be used instead of numeric // if the value can't be parsed as float then we assume that string comparison should be used instead of numeric
where += `${accessor} ${filter.operator} ?`; whereStmt += `${accessor} ${filter.operator} ?`;
params.push(filter.value); params.push(filter.value);
} } else {
else { whereStmt += `CAST(${accessor} AS DECIMAL) ${filter.operator} ?`;
where += `CAST(${accessor} AS DECIMAL) ${filter.operator} ?`; params.push(floatParam);
params.push(floatParam); }
} else {
throw new Error("Unknown operator " + filter.operator);
} }
} }
else {
throw new Error("Unknown operator " + filter.operator); return whereStmt;
}
} }
const where = parseWhereFilters(filters);
if (orderBy.length === 0) { if (orderBy.length === 0) {
// if no ordering is given then order at least by note title // if no ordering is given then order at least by note title
orderBy.push("notes.title"); orderBy.push("notes.title");

View File

@@ -13,6 +13,10 @@ function getSourceId() {
return namespace.get('sourceId'); return namespace.get('sourceId');
} }
function getLocalNowDateTime() {
return namespace.get('localNowDateTime');
}
function disableEntityEvents() { function disableEntityEvents() {
namespace.set('disableEntityEvents', true); namespace.set('disableEntityEvents', true);
} }
@@ -50,6 +54,7 @@ module.exports = {
wrap, wrap,
namespace, namespace,
getSourceId, getSourceId,
getLocalNowDateTime,
disableEntityEvents, disableEntityEvents,
isEntityEventsDisabled, isEntityEventsDisabled,
reset, reset,

View File

@@ -1,17 +1,29 @@
const dayjs = require('dayjs'); const dayjs = require('dayjs');
const cls = require('./cls');
function utcNowDateTime() { function utcNowDateTime() {
return utcDateStr(new Date()); return utcDateStr(new Date());
} }
// CLS date time is important in web deployments - server often runs in different time zone than user is located in
// so we'd prefer client timezone to be used to record local dates. For this reason requests from client contain
// "trilium-local-now-datetime" header which is then stored in CLS
function localNowDateTime() { function localNowDateTime() {
return dayjs().format('YYYY-MM-DD HH:mm:ss.SSSZZ') return cls.getLocalNowDateTime()
|| dayjs().format('YYYY-MM-DD HH:mm:ss.SSSZZ')
} }
function localNowDate() { function localNowDate() {
const date = new Date(); const clsDateTime = cls.getLocalNowDateTime();
return date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()); if (clsDateTime) {
return clsDateTime.substr(0, 10);
}
else {
const date = new Date();
return date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate());
}
} }
function pad(num) { function pad(num) {

View File

@@ -1,5 +1,5 @@
module.exports = { module.exports = {
isDev: function () { isDev: function () {
return process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === 'dev'; return !!(process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === 'dev');
} }
}; };

View File

@@ -60,6 +60,20 @@ module.exports = function (searchText) {
operator: '*=*', operator: '*=*',
value: searchText value: searchText
}); });
filters.push({
relation: 'or',
name: 'attributeName',
operator: '*=*',
value: searchText
});
filters.push({
relation: 'or',
name: 'attributeValue',
operator: '*=*',
value: searchText
});
} }
else { else {
const tokens = searchText.split(/\s+/); const tokens = searchText.split(/\s+/);
@@ -67,9 +81,27 @@ module.exports = function (searchText) {
for (const token of tokens) { for (const token of tokens) {
filters.push({ filters.push({
relation: 'and', relation: 'and',
name: 'text', name: 'sub',
operator: '*=*', children: [
value: token {
relation: 'or',
name: 'text',
operator: '*=*',
value: token
},
{
relation: 'or',
name: 'attributeName',
operator: '*=*',
value: token
},
{
relation: 'or',
name: 'attributeValue',
operator: '*=*',
value: token
}
]
}); });
} }
} }

View File

@@ -7,6 +7,7 @@ const log = require('./log');
const sqlInit = require('./sql_init'); const sqlInit = require('./sql_init');
const cls = require('./cls'); const cls = require('./cls');
const keyboardActionsService = require('./keyboard_actions'); const keyboardActionsService = require('./keyboard_actions');
const {ipcMain} = require('electron');
// Prevent window being garbage collected // Prevent window being garbage collected
/** @type {Electron.BrowserWindow} */ /** @type {Electron.BrowserWindow} */
@@ -14,6 +15,29 @@ let mainWindow;
/** @type {Electron.BrowserWindow} */ /** @type {Electron.BrowserWindow} */
let setupWindow; let setupWindow;
async function createExtraWindow(notePath) {
const {BrowserWindow} = require('electron');
const win = new BrowserWindow({
width: 1000,
height: 800,
title: 'Trilium Notes',
webPreferences: {
enableRemoteModule: true,
nodeIntegration: true,
spellcheck: await optionService.getOptionBool('spellCheckEnabled')
},
frame: await optionService.getOptionBool('nativeTitleBarVisible'),
icon: getIcon()
});
win.setMenuBarVisibility(false);
win.loadURL('http://127.0.0.1:' + await port + '/?extra=1#' + notePath);
}
ipcMain.on('create-extra-window', (event, arg) => {
createExtraWindow(arg.notePath);
});
async function createMainWindow() { async function createMainWindow() {
const windowStateKeeper = require('electron-window-state'); // should not be statically imported const windowStateKeeper = require('electron-window-state'); // should not be statically imported
@@ -141,5 +165,6 @@ module.exports = {
createMainWindow, createMainWindow,
createSetupWindow, createSetupWindow,
closeSetupWindow, closeSetupWindow,
createExtraWindow,
registerGlobalShortcuts registerGlobalShortcuts
}; };

View File

@@ -46,8 +46,9 @@
maxSyncIdAtLoad: <%= maxSyncIdAtLoad %>, maxSyncIdAtLoad: <%= maxSyncIdAtLoad %>,
instanceName: '<%= instanceName %>', instanceName: '<%= instanceName %>',
csrfToken: '<%= csrfToken %>', csrfToken: '<%= csrfToken %>',
isDev: '<%= isDev %>', isDev: <%= isDev %>,
appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %> appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %>,
isMainWindow: <%= isMainWindow %>
}; };
</script> </script>

View File

@@ -29,7 +29,7 @@
<div id="note-revision-title-buttons"></div> <div id="note-revision-title-buttons"></div>
</div> </div>
<div id="note-revision-content" style="overflow: auto;"></div> <div id="note-revision-content" style="overflow: auto; word-break: break-word;"></div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -114,7 +114,7 @@
maxSyncIdAtLoad: <%= maxSyncIdAtLoad %>, maxSyncIdAtLoad: <%= maxSyncIdAtLoad %>,
instanceName: '<%= instanceName %>', instanceName: '<%= instanceName %>',
csrfToken: '<%= csrfToken %>', csrfToken: '<%= csrfToken %>',
isDev: '<%= isDev %>', isDev: <%= isDev %>,
appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %> appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %>
}; };
</script> </script>