mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Compare commits
	
		
			25 Commits
		
	
	
		
			v0.41.4-be
			...
			v0.41.6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 5769587305 | ||
|  | ffbfccb701 | ||
|  | 907cdd8fcb | ||
|  | 7ea53d468e | ||
|  | 586d6b4557 | ||
|  | 8f68ff1932 | ||
|  | a1ea2c9115 | ||
|  | e8ce81a133 | ||
|  | aff12950f0 | ||
|  | 75c58cbf79 | ||
|  | 87a1e98fa2 | ||
|  | d1eacbb574 | ||
|  | 71d248cd87 | ||
|  | ac608b9334 | ||
|  | 32020d78b5 | ||
|  | ff853c7d0a | ||
|  | 8526cb2315 | ||
|  | dc89f72e75 | ||
|  | 657ff16267 | ||
|  | ed759f5585 | ||
|  | a86177bb59 | ||
|  | 9f1b3cc892 | ||
|  | 8473f72ec8 | ||
|  | 666d202a3a | ||
|  | 988fae50cb | 
							
								
								
									
										2
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							| @@ -5,7 +5,7 @@ | ||||
|       <driver-ref>sqlite.xerial</driver-ref> | ||||
|       <synchronize>true</synchronize> | ||||
|       <jdbc-driver>org.sqlite.JDBC</jdbc-driver> | ||||
|       <jdbc-url>jdbc:sqlite:$PROJECT_DIR$/../trilium-data/document.db</jdbc-url> | ||||
|       <jdbc-url>jdbc:sqlite:$USER_HOME$/trilium-data/document.db</jdbc-url> | ||||
|     </data-source> | ||||
|   </component> | ||||
| </project> | ||||
| @@ -1,8 +1,8 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <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"> | ||||
|       <ServerVersion>3.25.1</ServerVersion> | ||||
|       <ServerVersion>3.16.1</ServerVersion> | ||||
|     </root> | ||||
|     <schema id="2" parent="1" name="main"> | ||||
|       <Current>1</Current> | ||||
| @@ -57,6 +57,7 @@ | ||||
|     <index id="24" parent="6" name="sqlite_autoindex_api_tokens_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>apiTokenId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="25" parent="6"> | ||||
| @@ -130,17 +131,21 @@ | ||||
|     <index id="38" parent="7" name="sqlite_autoindex_attributes_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>attributeId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="39" parent="7" name="IDX_attributes_noteId_index"> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="40" parent="7" name="IDX_attributes_name_value"> | ||||
|       <ColNames>name | ||||
| value</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="41" parent="7" name="IDX_attributes_value_index"> | ||||
|       <ColNames>value</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="42" parent="7"> | ||||
|       <ColNames>attributeId</ColNames> | ||||
| @@ -207,14 +212,17 @@ value</ColNames> | ||||
|     <index id="54" parent="8" name="sqlite_autoindex_branches_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>branchId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="55" parent="8" name="IDX_branches_noteId_parentNoteId"> | ||||
|       <ColNames>noteId | ||||
| parentNoteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="56" parent="8" name="IDX_branches_parentNoteId"> | ||||
|       <ColNames>parentNoteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="57" parent="8"> | ||||
|       <ColNames>branchId</ColNames> | ||||
| @@ -245,6 +253,7 @@ parentNoteId</ColNames> | ||||
|     <index id="62" parent="9" name="sqlite_autoindex_note_contents_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="63" parent="9"> | ||||
| @@ -275,6 +284,7 @@ parentNoteId</ColNames> | ||||
|     <index id="68" parent="10" name="sqlite_autoindex_note_revision_contents_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>noteRevisionId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="69" parent="10"> | ||||
| @@ -359,22 +369,28 @@ parentNoteId</ColNames> | ||||
|     <index id="84" parent="11" name="sqlite_autoindex_note_revisions_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>noteRevisionId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="85" parent="11" name="IDX_note_revisions_noteId"> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="86" parent="11" name="IDX_note_revisions_utcDateLastEdited"> | ||||
|       <ColNames>utcDateLastEdited</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="87" parent="11" name="IDX_note_revisions_utcDateCreated"> | ||||
|       <ColNames>utcDateCreated</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="88" parent="11" name="IDX_note_revisions_dateLastEdited"> | ||||
|       <ColNames>dateLastEdited</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="89" parent="11" name="IDX_note_revisions_dateCreated"> | ||||
|       <ColNames>dateCreated</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="90" parent="11"> | ||||
|       <ColNames>noteRevisionId</ColNames> | ||||
| @@ -461,28 +477,36 @@ parentNoteId</ColNames> | ||||
|     <index id="105" parent="12" name="sqlite_autoindex_notes_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="106" parent="12" name="IDX_notes_title"> | ||||
|       <ColNames>title</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="107" parent="12" name="IDX_notes_type"> | ||||
|       <ColNames>type</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="108" parent="12" name="IDX_notes_isDeleted"> | ||||
|       <ColNames>isDeleted</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="109" parent="12" name="IDX_notes_dateCreated"> | ||||
|       <ColNames>dateCreated</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="110" parent="12" name="IDX_notes_dateModified"> | ||||
|       <ColNames>dateModified</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="111" parent="12" name="IDX_notes_utcDateCreated"> | ||||
|       <ColNames>utcDateCreated</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <index id="112" parent="12" name="IDX_notes_utcDateModified"> | ||||
|       <ColNames>utcDateModified</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="113" parent="12"> | ||||
|       <ColNames>noteId</ColNames> | ||||
| @@ -523,6 +547,7 @@ parentNoteId</ColNames> | ||||
|     <index id="120" parent="13" name="sqlite_autoindex_options_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>name</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="121" parent="13"> | ||||
| @@ -558,6 +583,7 @@ parentNoteId</ColNames> | ||||
|     <index id="127" parent="14" name="sqlite_autoindex_recent_notes_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>noteId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <key id="128" parent="14"> | ||||
| @@ -578,10 +604,12 @@ parentNoteId</ColNames> | ||||
|     <index id="131" parent="15" name="sqlite_autoindex_source_ids_1"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ColNames>sourceId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="132" parent="15" name="IDX_source_ids_utcDateCreated"> | ||||
|       <ColNames>utcDateCreated</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="133" parent="15"> | ||||
|       <ColNames>sourceId</ColNames> | ||||
| @@ -602,7 +630,7 @@ parentNoteId</ColNames> | ||||
|     </column> | ||||
|     <column id="137" parent="16" name="rootpage"> | ||||
|       <Position>4</Position> | ||||
|       <DataType>int|0s</DataType> | ||||
|       <DataType>integer|0s</DataType> | ||||
|     </column> | ||||
|     <column id="138" parent="16" name="sql"> | ||||
|       <Position>5</Position> | ||||
| @@ -649,10 +677,12 @@ parentNoteId</ColNames> | ||||
|     <index id="147" parent="18" name="IDX_sync_entityName_entityId"> | ||||
|       <ColNames>entityName | ||||
| entityId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="148" parent="18" name="IDX_sync_utcSyncDate"> | ||||
|       <ColNames>utcSyncDate</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="149" parent="18"> | ||||
|       <ColNames>id</ColNames> | ||||
|   | ||||
							
								
								
									
										1
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							| @@ -2,5 +2,6 @@ | ||||
| <project version="4"> | ||||
|   <component name="VcsDirectoryMappings"> | ||||
|     <mapping directory="" vcs="Git" /> | ||||
|     <mapping directory="$PROJECT_DIR$/../.." vcs="Git" /> | ||||
|   </component> | ||||
| </project> | ||||
| @@ -8,7 +8,7 @@ Trilium Notes is a hierarchical note taking application with focus on building l | ||||
| ## Features | ||||
|  | ||||
| * Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://github.com/zadam/trilium/wiki/Cloning-notes)) | ||||
| * Rich WYSIWYG note editing including e.g. tables and images with markdown [autoformat](https://github.com/zadam/trilium/wiki/Text-editor#autoformat) | ||||
| * Rich WYSIWYG note editing including e.g. tables and images with markdown [autoformat](https://github.com/zadam/trilium/wiki/Text-notes#autoformat) | ||||
| * Support for editing [notes with source code](https://github.com/zadam/trilium/wiki/Code-notes), including syntax highlighting | ||||
| * Fast and easy [navigation between notes](https://github.com/zadam/trilium/wiki/Note-navigation), full text search and [note hoisting](https://github.com/zadam/trilium/wiki/Note-hoisting) | ||||
| * Seamless [note versioning](https://github.com/zadam/trilium/wiki/Note-revisions) | ||||
|   | ||||
| @@ -5,6 +5,8 @@ if [[ $# -eq 0 ]] ; then | ||||
|     exit 1 | ||||
| fi | ||||
|  | ||||
| npm run webpack | ||||
|  | ||||
| DIR=$1 | ||||
|  | ||||
| rm -rf $DIR | ||||
| @@ -25,7 +27,7 @@ cp -r electron.js $DIR/ | ||||
| cp webpack-* $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 | ||||
|  | ||||
|   | ||||
| @@ -879,7 +879,7 @@ | ||||
| </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><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> | ||||
|  | ||||
| <br class="clear"> | ||||
|   | ||||
| @@ -5483,7 +5483,7 @@ Typical use case is when new note has been created, we should wait until it is s | ||||
| </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><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> | ||||
|  | ||||
| <br class="clear"> | ||||
|   | ||||
| @@ -507,7 +507,7 @@ | ||||
| </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><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> | ||||
|  | ||||
| <br class="clear"> | ||||
|   | ||||
| @@ -6813,7 +6813,7 @@ Cache is note instance scoped. | ||||
| </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><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> | ||||
|  | ||||
| <br class="clear"> | ||||
|   | ||||
| @@ -79,7 +79,7 @@ export default Attribute;</code></pre> | ||||
| </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><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> | ||||
|  | ||||
| <br class="clear"> | ||||
|   | ||||
| @@ -81,7 +81,7 @@ export default Branch;</code></pre> | ||||
| </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><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> | ||||
|  | ||||
| <br class="clear"> | ||||
|   | ||||
| @@ -61,7 +61,7 @@ export default NoteComplement;</code></pre> | ||||
| </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><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> | ||||
|  | ||||
| <br class="clear"> | ||||
|   | ||||
| @@ -493,7 +493,7 @@ export default NoteShort;</code></pre> | ||||
| </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><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> | ||||
|  | ||||
| <br class="clear"> | ||||
|   | ||||
| @@ -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> | ||||
|  | ||||
| <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> | ||||
|  | ||||
| <br class="clear"> | ||||
|   | ||||
| @@ -50,7 +50,7 @@ | ||||
| </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><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> | ||||
|  | ||||
| <br class="clear"> | ||||
|   | ||||
| @@ -443,7 +443,7 @@ export default FrontendScriptApi;</code></pre> | ||||
| </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><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> | ||||
|  | ||||
| <br class="clear"> | ||||
|   | ||||
							
								
								
									
										151
									
								
								docs/frontend_api/widgets_collapsible_widget.js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								docs/frontend_api/widgets_collapsible_widget.js.html
									
									
									
									
									
										Normal 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 = ` | ||||
| <div class="card widget"> | ||||
|     <div class="card-header"> | ||||
|         <div>            | ||||
|             <button class="btn btn-sm widget-title" data-toggle="collapse" data-target="#[to be set]"> | ||||
|                 Collapsible Group Item | ||||
|             </button> | ||||
|              | ||||
|             <a class="widget-help external no-arrow bx bx-info-circle"></a> | ||||
|         </div> | ||||
|          | ||||
|         <div class="widget-header-actions"></div> | ||||
|     </div> | ||||
|  | ||||
|     <div id="[to be set]" class="collapse body-wrapper" style="transition: none; "> | ||||
|         <div class="card-body"></div> | ||||
|     </div> | ||||
| </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> | ||||
							
								
								
									
										709
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										709
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										26
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								package.json
									
									
									
									
									
								
							| @@ -2,7 +2,7 @@ | ||||
|   "name": "trilium", | ||||
|   "productName": "Trilium Notes", | ||||
|   "description": "Trilium Notes", | ||||
|   "version": "0.41.4-beta", | ||||
|   "version": "0.41.5", | ||||
|   "license": "AGPL-3.0-only", | ||||
|   "main": "electron.js", | ||||
|   "bin": { | ||||
| @@ -16,7 +16,7 @@ | ||||
|     "start-server": "TRILIUM_ENV=dev node ./src/www", | ||||
|     "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-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", | ||||
|     "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", | ||||
|     "cookie-parser": "1.4.5", | ||||
|     "csurf": "1.11.0", | ||||
|     "dayjs": "1.8.24", | ||||
|     "dayjs": "1.8.25", | ||||
|     "debug": "4.1.1", | ||||
|     "ejs": "3.0.2", | ||||
|     "ejs": "3.1.2", | ||||
|     "electron-debug": "3.0.1", | ||||
|     "electron-dl": "3.0.0", | ||||
|     "electron-find": "1.0.6", | ||||
|     "electron-window-state": "5.0.3", | ||||
|     "express": "4.17.1", | ||||
|     "express-session": "1.17.0", | ||||
|     "file-type": "14.1.4", | ||||
|     "express-session": "1.17.1", | ||||
|     "file-type": "14.2.0", | ||||
|     "fs-extra": "9.0.0", | ||||
|     "helmet": "3.22.0", | ||||
|     "html": "1.0.0", | ||||
| @@ -51,10 +51,10 @@ | ||||
|     "imagemin-pngquant": "8.0.0", | ||||
|     "ini": "1.3.5", | ||||
|     "is-svg": "4.2.1", | ||||
|     "jimp": "0.10.2", | ||||
|     "mime-types": "2.1.26", | ||||
|     "jimp": "0.10.3", | ||||
|     "mime-types": "2.1.27", | ||||
|     "multer": "1.4.2", | ||||
|     "node-abi": "2.15.0", | ||||
|     "node-abi": "2.16.0", | ||||
|     "open": "7.0.3", | ||||
|     "portscanner": "2.2.0", | ||||
|     "rand-token": "1.0.1", | ||||
| @@ -73,18 +73,18 @@ | ||||
|     "turndown": "6.0.0", | ||||
|     "turndown-plugin-gfm": "1.0.2", | ||||
|     "unescape": "1.0.1", | ||||
|     "ws": "7.2.3", | ||||
|     "ws": "7.2.5", | ||||
|     "yauzl": "^2.10.0", | ||||
|     "yazl": "^2.5.1" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "electron": "9.0.0-beta.16", | ||||
|     "electron-builder": "22.4.1", | ||||
|     "electron": "9.0.0-beta.20", | ||||
|     "electron-builder": "22.5.1", | ||||
|     "electron-packager": "14.2.1", | ||||
|     "electron-rebuild": "1.10.1", | ||||
|     "jsdoc": "3.6.4", | ||||
|     "lorem-ipsum": "2.0.3", | ||||
|     "webpack": "5.0.0-beta.14", | ||||
|     "webpack": "5.0.0-beta.15", | ||||
|     "webpack-cli": "4.0.0-beta.8" | ||||
|   }, | ||||
|   "optionalDependencies": { | ||||
|   | ||||
| @@ -5,8 +5,9 @@ import bundleService from "./services/bundle.js"; | ||||
| import noteAutocompleteService from './services/note_autocomplete.js'; | ||||
| import macInit from './services/mac_init.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 DesktopExtraWindowLayout from "./layouts/desktop_extra_window_layout.js"; | ||||
|  | ||||
| glob.setupGlobs(); | ||||
|  | ||||
| @@ -23,9 +24,11 @@ $('[data-toggle="tooltip"]').tooltip({ | ||||
| macInit.init(); | ||||
|  | ||||
| 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(); | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import optionsService from "../../services/options.js"; | ||||
| import utils from "../../services/utils.js"; | ||||
| import server from "../../services/server.js"; | ||||
| import toastService from "../../services/toast.js"; | ||||
|  | ||||
| @@ -20,7 +20,9 @@ const TPL = ` | ||||
|         <input type="text" class="form-control" id="spell-check-language-code" placeholder="for example "en-US", "de-AT""> | ||||
|     </div> | ||||
|  | ||||
|     <p>Multiple languages can be separated by comman. Changes to the spell check options will take effect after application restart.</p> | ||||
|     <p>Multiple languages can be separated by comma, e.g. <code>en-US, de-DE, cs</code>. Changes to the spell check options will take effect after application restart.</p> | ||||
|      | ||||
|     <p><strong>Available language codes: </strong> <span id="available-language-codes"></span></p> | ||||
| </div> | ||||
|  | ||||
| <div> | ||||
| @@ -95,6 +97,14 @@ export default class ProtectedSessionOptions { | ||||
|             return false; | ||||
|         }); | ||||
|  | ||||
|         this.$availableLanguageCodes = $("#available-language-codes"); | ||||
|  | ||||
|         if (utils.isElectron()) { | ||||
|             const {webContents} = utils.dynamicRequire('electron').remote.getCurrentWindow(); | ||||
|  | ||||
|             this.$availableLanguageCodes.text(webContents.session.availableSpellCheckerLanguages.join(', ')); | ||||
|         } | ||||
|  | ||||
|         this.$eraseNotesAfterTimeInSeconds = $("#erase-notes-after-time-in-seconds"); | ||||
|  | ||||
|         this.$eraseNotesAfterTimeInSeconds.on('change', () => { | ||||
|   | ||||
| @@ -32,10 +32,10 @@ export async function showDialog(ancestorNoteId) { | ||||
|     for (const [dateDay, dayChanges] of groupedByDate) { | ||||
|         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) { | ||||
|             const formattedTime = utils.formatTime(utils.parseDate(change.date)); | ||||
|             const formattedTime = change.date.substr(11, 5); | ||||
|  | ||||
|             let $noteLink; | ||||
|  | ||||
| @@ -82,7 +82,12 @@ export async function showDialog(ancestorNoteId) { | ||||
|             } | ||||
|  | ||||
|             $changesList.append($('<li>') | ||||
|                 .append(formattedTime + ' - ') | ||||
|                 .append( | ||||
|                     $("<span>") | ||||
|                         .text(formattedTime) | ||||
|                         .attr("title", change.date) | ||||
|                 ) | ||||
|                 .append(' - ') | ||||
|                 .append($noteLink)); | ||||
|         } | ||||
|  | ||||
| @@ -92,23 +97,9 @@ export async function showDialog(ancestorNoteId) { | ||||
|  | ||||
| function groupByDate(result) { | ||||
|     const groupedByDate = new Map(); | ||||
|     const dayCache = {}; | ||||
|  | ||||
|     for (const row of result) { | ||||
|         let dateDay = utils.parseDate(row.date); | ||||
|         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; | ||||
|         } | ||||
|         const dateDay = row.date.substr(0, 10); | ||||
|  | ||||
|         if (!groupedByDate.has(dateDay)) { | ||||
|             groupedByDate.set(dateDay, []); | ||||
|   | ||||
							
								
								
									
										48
									
								
								src/public/app/layouts/desktop_extra_window_layout.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/public/app/layouts/desktop_extra_window_layout.js
									
									
									
									
									
										Normal 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')) | ||||
|                 ) | ||||
|             ); | ||||
|     } | ||||
| } | ||||
| @@ -1,30 +1,30 @@ | ||||
| import FlexContainer from "./flex_container.js"; | ||||
| import GlobalMenuWidget from "./global_menu.js"; | ||||
| import TabRowWidget from "./tab_row.js"; | ||||
| import TitleBarButtonsWidget from "./title_bar_buttons.js"; | ||||
| import StandardTopWidget from "./standard_top_widget.js"; | ||||
| import SidePaneContainer from "./side_pane_container.js"; | ||||
| import GlobalButtonsWidget from "./global_buttons.js"; | ||||
| import SearchBoxWidget from "./search_box.js"; | ||||
| import SearchResultsWidget from "./search_results.js"; | ||||
| import NoteTreeWidget from "./note_tree.js"; | ||||
| import TabCachingWidget from "./tab_caching_widget.js"; | ||||
| import NotePathsWidget from "./note_paths.js"; | ||||
| import NoteTitleWidget from "./note_title.js"; | ||||
| import RunScriptButtonsWidget from "./run_script_buttons.js"; | ||||
| import ProtectedNoteSwitchWidget from "./protected_note_switch.js"; | ||||
| import NoteTypeWidget from "./note_type.js"; | ||||
| import NoteActionsWidget from "./note_actions.js"; | ||||
| import PromotedAttributesWidget from "./promoted_attributes.js"; | ||||
| import NoteDetailWidget from "./note_detail.js"; | ||||
| import NoteInfoWidget from "./note_info.js"; | ||||
| import CalendarWidget from "./calendar.js"; | ||||
| import AttributesWidget from "./attributes.js"; | ||||
| import LinkMapWidget from "./link_map.js"; | ||||
| import NoteRevisionsWidget from "./note_revisions.js"; | ||||
| import SimilarNotesWidget from "./similar_notes.js"; | ||||
| import WhatLinksHereWidget from "./what_links_here.js"; | ||||
| import SidePaneToggles from "./side_pane_toggles.js"; | ||||
| 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 StandardTopWidget from "../widgets/standard_top_widget.js"; | ||||
| import SidePaneContainer from "../widgets/side_pane_container.js"; | ||||
| import GlobalButtonsWidget from "../widgets/global_buttons.js"; | ||||
| import SearchBoxWidget from "../widgets/search_box.js"; | ||||
| import SearchResultsWidget from "../widgets/search_results.js"; | ||||
| import NoteTreeWidget from "../widgets/note_tree.js"; | ||||
| import TabCachingWidget from "../widgets/tab_caching_widget.js"; | ||||
| import NotePathsWidget from "../widgets/note_paths.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"; | ||||
| import NoteInfoWidget from "../widgets/collapsible_widgets/note_info.js"; | ||||
| import CalendarWidget from "../widgets/collapsible_widgets/calendar.js"; | ||||
| import AttributesWidget from "../widgets/collapsible_widgets/attributes.js"; | ||||
| import LinkMapWidget from "../widgets/collapsible_widgets/link_map.js"; | ||||
| import NoteRevisionsWidget from "../widgets/collapsible_widgets/note_revisions.js"; | ||||
| import SimilarNotesWidget from "../widgets/collapsible_widgets/similar_notes.js"; | ||||
| import WhatLinksHereWidget from "../widgets/collapsible_widgets/what_links_here.js"; | ||||
| import SidePaneToggles from "../widgets/side_pane_toggles.js"; | ||||
| import appContext from "../services/app_context.js"; | ||||
| 
 | ||||
| const RIGHT_PANE_CSS = ` | ||||
| @@ -98,7 +98,7 @@ const RIGHT_PANE_CSS = ` | ||||
| } | ||||
| </style>`; | ||||
| 
 | ||||
| export default class DesktopLayout { | ||||
| export default class DesktopMainWindowLayout { | ||||
|     constructor(customWidgets) { | ||||
|         this.customWidgets = customWidgets; | ||||
|     } | ||||
| @@ -1,11 +1,11 @@ | ||||
| import FlexContainer from "./flex_container.js"; | ||||
| import NoteTitleWidget from "./note_title.js"; | ||||
| import NoteDetailWidget from "./note_detail.js"; | ||||
| import NoteTreeWidget from "./note_tree.js"; | ||||
| import MobileGlobalButtonsWidget from "./mobile_global_buttons.js"; | ||||
| import CloseDetailButtonWidget from "./close_detail_button.js"; | ||||
| import MobileDetailMenuWidget from "./mobile_detail_menu.js"; | ||||
| import ScreenContainer from "./screen_container.js"; | ||||
| import FlexContainer from "../widgets/flex_container.js"; | ||||
| import NoteTitleWidget from "../widgets/note_title.js"; | ||||
| import NoteDetailWidget from "../widgets/note_detail.js"; | ||||
| import NoteTreeWidget from "../widgets/note_tree.js"; | ||||
| import MobileGlobalButtonsWidget from "../widgets/mobile_widgets/mobile_global_buttons.js"; | ||||
| import CloseDetailButtonWidget from "../widgets/mobile_widgets/close_detail_button.js"; | ||||
| import MobileDetailMenuWidget from "../widgets/mobile_widgets/mobile_detail_menu.js"; | ||||
| import ScreenContainer from "../widgets/mobile_widgets/screen_container.js"; | ||||
| 
 | ||||
| const MOBILE_CSS = ` | ||||
| <style> | ||||
| @@ -1,5 +1,5 @@ | ||||
| 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"; | ||||
|  | ||||
| glob.setupGlobs(); | ||||
|   | ||||
| @@ -9,10 +9,16 @@ import TabManager from "./tab_manager.js"; | ||||
| import treeService from "./tree.js"; | ||||
| import Component from "../widgets/component.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"; | ||||
|  | ||||
| class AppContext extends Component { | ||||
|     constructor(isMainWindow) { | ||||
|         super(); | ||||
|  | ||||
|         this.isMainWindow = isMainWindow; | ||||
|     } | ||||
|  | ||||
|     setLayout(layout) { | ||||
|         this.layout = layout; | ||||
|     } | ||||
| @@ -95,14 +101,21 @@ class AppContext extends Component { | ||||
|         return $(el).closest(".component").prop('component'); | ||||
|     } | ||||
|  | ||||
|     async protectedSessionStartedEvent() { | ||||
|         await treeCache.loadInitialTree(); | ||||
|     async openInNewWindow(notePath) { | ||||
|         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 | ||||
| $(window).on('beforeunload', () => { | ||||
|   | ||||
| @@ -113,12 +113,16 @@ function newTabContextMenu(e) { | ||||
|         x: e.pageX, | ||||
|         y: e.pageY, | ||||
|         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}) => { | ||||
|             if (command === 'openNoteInNewTab') { | ||||
|                 appContext.tabManager.openTabWithNote(notePath); | ||||
|             } | ||||
|             else if (command === 'openNoteInNewWindow') { | ||||
|                 appContext.openInNewWindow(notePath); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| @@ -162,6 +166,7 @@ $(document).on('contextmenu', 'a.ck-link-actions__preview', newTabContextMenu); | ||||
| $(document).on('contextmenu', '.note-detail-text a', newTabContextMenu); | ||||
| $(document).on('contextmenu', "a[data-action='note']", newTabContextMenu); | ||||
| $(document).on('contextmenu', ".note-detail-render a", newTabContextMenu); | ||||
| $(document).on('contextmenu', ".note-paths-widget a", newTabContextMenu); | ||||
|  | ||||
| export default { | ||||
|     getNotePathFromUrl, | ||||
|   | ||||
| @@ -1,13 +1,10 @@ | ||||
| import treeService from './tree.js'; | ||||
| import utils from './utils.js'; | ||||
| import server from './server.js'; | ||||
| import protectedSessionHolder from './protected_session_holder.js'; | ||||
| import toastService from "./toast.js"; | ||||
| import ws from "./ws.js"; | ||||
| import appContext from "./app_context.js"; | ||||
|  | ||||
| const $enterProtectedSessionButton = $("#enter-protected-session-button"); | ||||
| const $leaveProtectedSessionButton = $("#leave-protected-session-button"); | ||||
| import treeCache from "./tree_cache.js"; | ||||
|  | ||||
| let protectedSessionDeferred = null; | ||||
|  | ||||
| @@ -45,6 +42,10 @@ async function setupProtectedSession(password) { | ||||
|     protectedSessionHolder.setProtectedSessionId(response.protectedSessionId); | ||||
|     protectedSessionHolder.touchProtectedSession(); | ||||
|  | ||||
|     await treeCache.loadInitialTree(); | ||||
|  | ||||
|     await appContext.triggerEvent('treeCacheReloaded'); | ||||
|  | ||||
|     appContext.triggerEvent('protectedSessionStarted'); | ||||
|  | ||||
|     if (protectedSessionDeferred !== null) { | ||||
| @@ -54,9 +55,6 @@ async function setupProtectedSession(password) { | ||||
|         protectedSessionDeferred = null; | ||||
|     } | ||||
|  | ||||
|     $enterProtectedSessionButton.hide(); | ||||
|     $leaveProtectedSessionButton.show(); | ||||
|  | ||||
|     toastService.showMessage("Protected session has been started."); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -39,9 +39,16 @@ function touchProtectedSession() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| function touchProtectedSessionIfNecessary(note) { | ||||
|     if (note && note.isProtected && isProtectedSessionAvailable()) { | ||||
|         touchProtectedSession(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default { | ||||
|     setProtectedSessionId, | ||||
|     resetProtectedSession, | ||||
|     isProtectedSessionAvailable, | ||||
|     touchProtectedSession | ||||
|     touchProtectedSession, | ||||
|     touchProtectedSessionIfNecessary | ||||
| }; | ||||
| @@ -8,6 +8,7 @@ function getHeaders(headers) { | ||||
|     // also avoiding using underscores instead of dashes since nginx filters them out by default | ||||
|     const allHeaders = { | ||||
|         'trilium-source-id': glob.sourceId, | ||||
|         'trilium-local-now-datetime': utils.localNowDateTime(), | ||||
|         'x-csrf-token': glob.csrfToken | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -58,6 +58,7 @@ class TabContext extends Component { | ||||
|  | ||||
|         this.autoBookDisabled = false; | ||||
|         this.textPreviewDisabled = false; | ||||
|         this.codePreviewDisabled = false; | ||||
|  | ||||
|         setTimeout(async () => { | ||||
|             // 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); | ||||
|  | ||||
|         if (this.note.isProtected && protectedSessionHolder.isProtectedSessionAvailable()) { | ||||
|             // FIXME: there are probably more places where this should be done | ||||
|             protectedSessionHolder.touchProtectedSession(); | ||||
|         } | ||||
|         protectedSessionHolder.touchProtectedSessionIfNecessary(this.note); | ||||
|  | ||||
|         if (triggerSwitchEvent) { | ||||
|             this.triggerEvent('tabNoteSwitched', { | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import treeCache from "./tree_cache.js"; | ||||
| import treeService from "./tree.js"; | ||||
| import utils from "./utils.js"; | ||||
| import TabContext from "./tab_context.js"; | ||||
| import appContext from "./app_context.js"; | ||||
|  | ||||
| export default class TabManager extends Component { | ||||
|     constructor() { | ||||
| @@ -14,6 +15,10 @@ export default class TabManager extends Component { | ||||
|         this.activeTabId = null; | ||||
|  | ||||
|         this.tabsUpdate = new SpacedUpdate(async () => { | ||||
|             if (!appContext.isMainWindow) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             const openTabs = this.tabContexts | ||||
|                 .map(tc => tc.getTabState()) | ||||
|                 .filter(t => !!t); | ||||
| @@ -30,7 +35,9 @@ export default class TabManager extends Component { | ||||
|     } | ||||
|  | ||||
|     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 | ||||
|         // (useful, among others, for opening clipped notes from clipper) | ||||
| @@ -39,17 +46,17 @@ export default class TabManager extends Component { | ||||
|             const noteId = treeService.getNoteIdFromNotePath(notePath); | ||||
|  | ||||
|             if (noteId && await treeCache.noteExists(noteId)) { | ||||
|                 for (const tab of openTabs) { | ||||
|                 for (const tab of tabsToOpen) { | ||||
|                     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) { | ||||
|                     foundTab.active = true; | ||||
|                 } | ||||
|                 else { | ||||
|                     openTabs.push({ | ||||
|                     tabsToOpen.push({ | ||||
|                         notePath: notePath, | ||||
|                         active: true | ||||
|                     }); | ||||
| @@ -59,7 +66,7 @@ export default class TabManager extends Component { | ||||
|  | ||||
|         let filteredTabs = []; | ||||
|  | ||||
|         for (const openTab of openTabs) { | ||||
|         for (const openTab of tabsToOpen) { | ||||
|             const noteId = treeService.getNoteIdFromNotePath(openTab.notePath); | ||||
|  | ||||
|             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}) { | ||||
|         if (hoistedNoteId === 'root') { | ||||
|             return; | ||||
|   | ||||
| @@ -98,9 +98,8 @@ async function prepareNode(branch) { | ||||
|         key: utils.randomString(12) // this should prevent some "duplicate key" errors | ||||
|     }; | ||||
|  | ||||
|     if (note.hasChildren() || note.type === 'search') { | ||||
|         node.folder = true; | ||||
|     } | ||||
|     node.folder = getChildBranchesWithoutImages(note).length > 0 | ||||
|                || note.type === 'search'; | ||||
|  | ||||
|     return node; | ||||
| } | ||||
| @@ -108,16 +107,9 @@ async function prepareNode(branch) { | ||||
| async function prepareRealBranch(parentNote) { | ||||
|     utils.assertArguments(parentNote); | ||||
|  | ||||
|     const childBranches = await parentNote.getChildBranches(); | ||||
|  | ||||
|     if (!childBranches) { | ||||
|         ws.logError(`No children for ${parentNote}. This shouldn't happen.`); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const noteList = []; | ||||
|  | ||||
|     for (const branch of childBranches) { | ||||
|     for (const branch of getChildBranchesWithoutImages(parentNote)) { | ||||
|         const node = await prepareNode(branch); | ||||
|  | ||||
|         noteList.push(node); | ||||
| @@ -126,6 +118,20 @@ async function prepareRealBranch(parentNote) { | ||||
|     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) { | ||||
|     await treeCache.reloadNotes([note.noteId]); | ||||
|  | ||||
| @@ -170,5 +176,6 @@ export default { | ||||
|     prepareRootNode, | ||||
|     prepareBranch, | ||||
|     getExtraClasses, | ||||
|     getIcon | ||||
|     getIcon, | ||||
|     getChildBranchesWithoutImages | ||||
| } | ||||
| @@ -56,7 +56,8 @@ class TreeContextMenu { | ||||
|         const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch; | ||||
|  | ||||
|         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", | ||||
|                 items: insertNoteAfterEnabled ? this.getNoteTypeItems("insertNoteAfter") : null, | ||||
|                 enabled: insertNoteAfterEnabled && noSelectedNotes }, | ||||
| @@ -111,6 +112,9 @@ class TreeContextMenu { | ||||
|         if (command === 'openInTab') { | ||||
|             appContext.tabManager.openTabWithNote(notePath); | ||||
|         } | ||||
|         else if (command === 'openInWindow') { | ||||
|             appContext.openInNewWindow(notePath); | ||||
|         } | ||||
|         else if (command === "insertNoteAfter") { | ||||
|             const parentNoteId = this.node.data.parentNoteId; | ||||
|             const isProtected = await treeService.getParentProtectedStatus(this.node); | ||||
|   | ||||
| @@ -40,6 +40,10 @@ function formatDateTime(date) { | ||||
|     return formatDate(date) + " " + formatTime(date); | ||||
| } | ||||
|  | ||||
| function localNowDateTime() { | ||||
|     return dayjs().format('YYYY-MM-DD HH:mm:ss.SSSZZ') | ||||
| } | ||||
|  | ||||
| function now() { | ||||
|     return formatTimeWithSeconds(new Date()); | ||||
| } | ||||
| @@ -239,7 +243,7 @@ function focusSavedElement() { | ||||
|     $lastFocusedElement = null; | ||||
| } | ||||
|  | ||||
| function openDialog($dialog) { | ||||
| async function openDialog($dialog) { | ||||
|     closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
| @@ -253,6 +257,9 @@ function openDialog($dialog) { | ||||
|             focusSavedElement(); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     const keyboardActionsService = (await import("./keyboard_actions.js")).default; | ||||
|     keyboardActionsService.updateDisplayedShortcuts($dialog); | ||||
| } | ||||
|  | ||||
| function isHtmlEmpty(html) { | ||||
| @@ -318,6 +325,7 @@ export default { | ||||
|     formatDate, | ||||
|     formatDateISO, | ||||
|     formatDateTime, | ||||
|     localNowDateTime, | ||||
|     now, | ||||
|     isElectron, | ||||
|     isMac, | ||||
|   | ||||
| @@ -57,7 +57,10 @@ class BasicWidget extends Component { | ||||
|         for (const key in this.attrs) { | ||||
|             if (key === 'style') { | ||||
|                 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 { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import utils from "../services/utils.js"; | ||||
| import linkService from "../services/link.js"; | ||||
| import ws from "../services/ws.js"; | ||||
| import CollapsibleWidget from "./collapsible_widget.js"; | ||||
| import utils from "../../services/utils.js"; | ||||
| import linkService from "../../services/link.js"; | ||||
| import ws from "../../services/ws.js"; | ||||
| import CollapsibleWidget from "../collapsible_widget.js"; | ||||
| 
 | ||||
| export default class AttributesWidget extends CollapsibleWidget { | ||||
|     get widgetTitle() { return "Attributes"; } | ||||
| @@ -16,7 +16,7 @@ export default class AttributesWidget extends CollapsibleWidget { | ||||
|     get headerActions() { | ||||
|         const $showFullButton = $("<a>").append("show dialog").addClass('widget-header-action'); | ||||
|         $showFullButton.on('click', async () => { | ||||
|             const attributesDialog = await import("../dialogs/attributes.js"); | ||||
|             const attributesDialog = await import("../../dialogs/attributes.js"); | ||||
|             attributesDialog.showDialog(); | ||||
|         }); | ||||
| 
 | ||||
| @@ -1,9 +1,9 @@ | ||||
| import CollapsibleWidget from "./collapsible_widget.js"; | ||||
| import libraryLoader from "../services/library_loader.js"; | ||||
| import utils from "../services/utils.js"; | ||||
| import dateNoteService from "../services/date_notes.js"; | ||||
| import server from "../services/server.js"; | ||||
| import appContext from "../services/app_context.js"; | ||||
| import CollapsibleWidget from "../collapsible_widget.js"; | ||||
| import libraryLoader from "../../services/library_loader.js"; | ||||
| import utils from "../../services/utils.js"; | ||||
| import dateNoteService from "../../services/date_notes.js"; | ||||
| import server from "../../services/server.js"; | ||||
| import appContext from "../../services/app_context.js"; | ||||
| 
 | ||||
| const TPL = ` | ||||
| <div class="calendar-widget"> | ||||
| @@ -1,7 +1,7 @@ | ||||
| import CollapsibleWidget from "./collapsible_widget.js"; | ||||
| import linkService from "../services/link.js"; | ||||
| import server from "../services/server.js"; | ||||
| import treeCache from "../services/tree_cache.js"; | ||||
| import CollapsibleWidget from "../collapsible_widget.js"; | ||||
| import linkService from "../../services/link.js"; | ||||
| import server from "../../services/server.js"; | ||||
| import treeCache from "../../services/tree_cache.js"; | ||||
| 
 | ||||
| export default class EditedNotesWidget extends CollapsibleWidget { | ||||
|     get widgetTitle() { return "Edited notes on this day"; } | ||||
| @@ -1,4 +1,4 @@ | ||||
| import CollapsibleWidget from "./collapsible_widget.js"; | ||||
| import CollapsibleWidget from "../collapsible_widget.js"; | ||||
| 
 | ||||
| let linkMapContainerIdCtr = 1; | ||||
| 
 | ||||
| @@ -21,7 +21,7 @@ export default class LinkMapWidget extends CollapsibleWidget { | ||||
|     get headerActions() { | ||||
|         const $showFullButton = $("<a>").append("show full").addClass('widget-header-action'); | ||||
|         $showFullButton.on('click', async () => { | ||||
|             const linkMapDialog = await import("../dialogs/link_map.js"); | ||||
|             const linkMapDialog = await import("../../dialogs/link_map.js"); | ||||
|             linkMapDialog.showDialog(); | ||||
|         }); | ||||
| 
 | ||||
| @@ -66,7 +66,7 @@ export default class LinkMapWidget extends CollapsibleWidget { | ||||
|         const $linkMapContainer = this.$body.find('.link-map-container'); | ||||
|         $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, { | ||||
|             maxDepth: 1, | ||||
| @@ -1,4 +1,4 @@ | ||||
| import CollapsibleWidget from "./collapsible_widget.js"; | ||||
| import CollapsibleWidget from "../collapsible_widget.js"; | ||||
| 
 | ||||
| const TPL = ` | ||||
| <table class="note-info-widget-table"> | ||||
| @@ -1,5 +1,5 @@ | ||||
| import server from "../services/server.js"; | ||||
| import CollapsibleWidget from "./collapsible_widget.js"; | ||||
| import server from "../../services/server.js"; | ||||
| import CollapsibleWidget from "../collapsible_widget.js"; | ||||
| 
 | ||||
| const TPL = ` | ||||
| <ul class="note-revision-list" style="max-height: 150px; overflow: auto;"> | ||||
| @@ -19,7 +19,7 @@ class NoteRevisionsWidget extends CollapsibleWidget { | ||||
|     get headerActions() { | ||||
|         const $showFullButton = $("<a>").append("show dialog").addClass('widget-header-action'); | ||||
|         $showFullButton.on('click', async () => { | ||||
|             const attributesDialog = await import("../dialogs/note_revisions.js"); | ||||
|             const attributesDialog = await import("../../dialogs/note_revisions.js"); | ||||
|             attributesDialog.showCurrentNoteRevisions(this.noteId); | ||||
|         }); | ||||
| 
 | ||||
| @@ -1,7 +1,7 @@ | ||||
| import CollapsibleWidget from "./collapsible_widget.js"; | ||||
| import linkService from "../services/link.js"; | ||||
| import server from "../services/server.js"; | ||||
| import treeCache from "../services/tree_cache.js"; | ||||
| import CollapsibleWidget from "../collapsible_widget.js"; | ||||
| import linkService from "../../services/link.js"; | ||||
| import server from "../../services/server.js"; | ||||
| import treeCache from "../../services/tree_cache.js"; | ||||
| 
 | ||||
| export default class SimilarNotesWidget extends CollapsibleWidget { | ||||
|     get widgetTitle() { return "Similar notes"; } | ||||
| @@ -1,5 +1,5 @@ | ||||
| import CollapsibleWidget from "./collapsible_widget.js"; | ||||
| import linkService from "../services/link.js"; | ||||
| import CollapsibleWidget from "../collapsible_widget.js"; | ||||
| import linkService from "../../services/link.js"; | ||||
| 
 | ||||
| export default class WhatLinksHereWidget extends CollapsibleWidget { | ||||
|     get widgetTitle() { return "What links here"; } | ||||
| @@ -13,7 +13,7 @@ export default class WhatLinksHereWidget extends CollapsibleWidget { | ||||
|     get headerActions() { | ||||
|         const $showFullButton = $("<a>").append("show link map").addClass('widget-header-action'); | ||||
|         $showFullButton.on('click', async () => { | ||||
|             const linkMapDialog = await import("../dialogs/link_map.js"); | ||||
|             const linkMapDialog = await import("../../dialogs/link_map.js"); | ||||
|             linkMapDialog.showDialog(); | ||||
|         }); | ||||
| 
 | ||||
| @@ -1,4 +1,4 @@ | ||||
| import BasicWidget from "./basic_widget.js"; | ||||
| import BasicWidget from "../basic_widget.js"; | ||||
| 
 | ||||
| const TPL = ` | ||||
| <button type="button" class="action-button d-sm-none d-md-none d-lg-none d-xl-none" aria-label="Close"> | ||||
| @@ -1,8 +1,8 @@ | ||||
| import BasicWidget from "./basic_widget.js"; | ||||
| import appContext from "../services/app_context.js"; | ||||
| import contextMenu from "../services/context_menu.js"; | ||||
| import noteCreateService from "../services/note_create.js"; | ||||
| import branchService from "../services/branches.js"; | ||||
| import BasicWidget from "../basic_widget.js"; | ||||
| import appContext from "../../services/app_context.js"; | ||||
| import contextMenu from "../../services/context_menu.js"; | ||||
| import noteCreateService from "../../services/note_create.js"; | ||||
| import branchService from "../../services/branches.js"; | ||||
| 
 | ||||
| const TPL = `<button type="button" class="action-button bx bx-menu"></button>`; | ||||
| 
 | ||||
| @@ -1,4 +1,4 @@ | ||||
| import BasicWidget from "./basic_widget.js"; | ||||
| import BasicWidget from "../basic_widget.js"; | ||||
| 
 | ||||
| const WIDGET_TPL = ` | ||||
| <div id="global-buttons"> | ||||
| @@ -1,4 +1,4 @@ | ||||
| import Component from "./component.js"; | ||||
| import Component from "../component.js"; | ||||
| 
 | ||||
| export default class MobileScreenSwitcherExecutor extends Component { | ||||
|     setActiveScreenCommand({screen}) { | ||||
| @@ -1,4 +1,4 @@ | ||||
| import FlexContainer from "./flex_container.js"; | ||||
| import FlexContainer from "../flex_container.js"; | ||||
| 
 | ||||
| export default class ScreenContainer extends FlexContainer { | ||||
|     constructor(screenName, direction) { | ||||
| @@ -6,7 +6,7 @@ import server from "../services/server.js"; | ||||
| import libraryLoader from "../services/library_loader.js"; | ||||
| import EmptyTypeWidget from "./type_widgets/empty.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 ImageTypeWidget from "./type_widgets/image.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 DeletedTypeWidget from "./type_widgets/deleted.js"; | ||||
| import ReadOnlyTextTypeWidget from "./type_widgets/read_only_text.js"; | ||||
| import ReadOnlyCodeTypeWidget from "./type_widgets/read_only_code.js"; | ||||
|  | ||||
| const TPL = ` | ||||
| <div class="note-detail"> | ||||
| @@ -38,7 +39,8 @@ const typeWidgetClasses = { | ||||
|     'deleted': DeletedTypeWidget, | ||||
|     'editable-text': EditableTextTypeWidget, | ||||
|     'read-only-text': ReadOnlyTextTypeWidget, | ||||
|     'code': CodeTypeWidget, | ||||
|     'editable-code': EditableCodeTypeWidget, | ||||
|     'read-only-code': ReadOnlyCodeTypeWidget, | ||||
|     'file': FileTypeWidget, | ||||
|     'image': ImageTypeWidget, | ||||
|     'search': SearchTypeWidget, | ||||
| @@ -61,6 +63,8 @@ export default class NoteDetailWidget extends TabAwareWidget { | ||||
|             const dto = note.dto; | ||||
|             dto.content = this.getTypeWidget().getContent(); | ||||
|  | ||||
|             protectedSessionHolder.touchProtectedSessionIfNecessary(note); | ||||
|  | ||||
|             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') { | ||||
|             type = 'editable-text'; | ||||
|         } | ||||
|  | ||||
|         if (type === 'code') { | ||||
|             type = 'editable-code'; | ||||
|         } | ||||
|  | ||||
|         if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) { | ||||
|             type = 'protected-session'; | ||||
|         } | ||||
| @@ -274,6 +291,12 @@ export default class NoteDetailWidget extends TabAwareWidget { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     codePreviewDisabledEvent({tabContext}) { | ||||
|         if (this.isTab(tabContext.tabId)) { | ||||
|             this.refresh(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async cutIntoNoteCommand() { | ||||
|         const note = appContext.tabManager.getActiveTabNote(); | ||||
|  | ||||
|   | ||||
| @@ -84,6 +84,7 @@ export default class NotePathsWidget extends TabAwareWidget { | ||||
|                 this.$currentPath.append( | ||||
|                     $("<a>") | ||||
|                         .attr('href', '#' + curPath) | ||||
|                         .attr('data-note-path', curPath) | ||||
|                         .addClass('no-tooltip-preview') | ||||
|                         .text(await treeService.getNoteTitle(noteId, parentNoteId)) | ||||
|                 ); | ||||
|   | ||||
| @@ -29,6 +29,8 @@ export default class NoteTitleWidget extends TabAwareWidget { | ||||
|         this.spacedUpdate = new SpacedUpdate(async () => { | ||||
|             const title = this.$noteTitle.val(); | ||||
|  | ||||
|             protectedSessionHolder.touchProtectedSessionIfNecessary(this.note); | ||||
|  | ||||
|             await server.put(`notes/${this.noteId}/change-title`, {title}); | ||||
|         }); | ||||
|     } | ||||
|   | ||||
| @@ -389,7 +389,8 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|  | ||||
|         node.data.isProtected = note.isProtected; | ||||
|         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.extraClasses = treeBuilder.getExtraClasses(note); | ||||
|         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 | ||||
|                 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()) { | ||||
|   | ||||
| @@ -5,12 +5,12 @@ const TPL = ` | ||||
| <div class="btn-group btn-group-xs"> | ||||
|     <button type="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 type="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> | ||||
| </div>`;``; | ||||
|  | ||||
|   | ||||
| @@ -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) { | ||||
|         // stop propagation of the event to the children, individual tab widget should not know about tab switching | ||||
|         // since they are per-tab | ||||
|         if (name === 'tabNoteSwitchedAndActivated') { | ||||
|             name = 'tabNoteSwitched'; | ||||
|         } | ||||
|  | ||||
|         if (name === 'tabNoteSwitched') { | ||||
|         if (['tabNoteSwitched', 'tabNoteSwitchedAndActivated'].includes(name)) { | ||||
|             // this event is propagated only to the widgets of a particular tab | ||||
|             const widget = this.widgets[data.tabContext.tabId]; | ||||
|  | ||||
|             if (widget) { | ||||
|                 return widget.handleEvent(name, data); | ||||
|             if (widget && (widget.hasBeenAlreadyShown || name === 'tabNoteSwitchedAndActivated')) { | ||||
|                 widget.hasBeenAlreadyShown = true; | ||||
|  | ||||
|                 return widget.handleEvent('tabNoteSwitched', data); | ||||
|             } | ||||
|             else { | ||||
|                 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 Promise.resolve(); | ||||
|     } | ||||
| } | ||||
| @@ -258,8 +258,9 @@ export default class TabRowWidget extends BasicWidget { | ||||
|                 x: e.pageX, | ||||
|                 y: e.pageY, | ||||
|                 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 except for this", command: "removeAllTabsExceptForThis", uiIcon: "empty"} | ||||
|                     {title: "Close all tabs except for this", command: "removeAllTabsExceptForThis", uiIcon: "empty"}, | ||||
|                 ], | ||||
|                 selectMenuItemHandler: ({command}) => { | ||||
|                     this.triggerCommand(command, {tabId}); | ||||
|   | ||||
| @@ -21,8 +21,8 @@ const TPL = ` | ||||
|     <div class="note-detail-code-editor"></div> | ||||
| </div>`; | ||||
| 
 | ||||
| export default class CodeTypeWidget extends TypeWidget { | ||||
|     static getType() { return "code"; } | ||||
| export default class EditableCodeTypeWidget extends TypeWidget { | ||||
|     static getType() { return "editable-code"; } | ||||
| 
 | ||||
|     doRender() { | ||||
|         this.$widget = $(TPL); | ||||
| @@ -1,13 +1,9 @@ | ||||
| import libraryLoader from "../../services/library_loader.js"; | ||||
| import noteAutocompleteService from '../../services/note_autocomplete.js'; | ||||
| import mimeTypesService from '../../services/mime_types.js'; | ||||
| import TypeWidget from "./type_widget.js"; | ||||
| import utils from "../../services/utils.js"; | ||||
| import appContext from "../../services/app_context.js"; | ||||
| import keyboardActionService from "../../services/keyboard_actions.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"; | ||||
|  | ||||
| const ENABLE_INSPECTOR = false; | ||||
| @@ -60,6 +56,7 @@ const TPL = ` | ||||
|         overflow: auto; | ||||
|         height: 100%; | ||||
|         font-family: var(--detail-text-font-family); | ||||
|         padding-left: 12px; | ||||
|     } | ||||
|      | ||||
|     .note-detail-text-editor { | ||||
|   | ||||
							
								
								
									
										44
									
								
								src/public/app/widgets/type_widgets/read_only_code.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/public/app/widgets/type_widgets/read_only_code.js
									
									
									
									
									
										Normal 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); | ||||
|     } | ||||
| } | ||||
| @@ -48,4 +48,10 @@ export default class TypeWidget extends TabAwareWidget { | ||||
|             this.refresh(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     codePreviewDisabledEvent({tabContext}) { | ||||
|         if (this.isTab(tabContext.tabId)) { | ||||
|             this.refresh(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -27,7 +27,7 @@ async function loginSync(req) { | ||||
|  | ||||
|     // login token is valid for 5 minutes | ||||
|     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; | ||||
|   | ||||
| @@ -18,13 +18,11 @@ async function getNoteRevision(req) { | ||||
|  | ||||
|     if (noteRevision.type === 'file') { | ||||
|         if (noteRevision.isStringNote()) { | ||||
|             await noteRevision.getContent(); | ||||
|  | ||||
|             noteRevision.content = noteRevision.content.substr(0, 10000); | ||||
|             noteRevision.content = (await noteRevision.getContent()).substr(0, 10000); | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         await noteRevision.getContent(); | ||||
|         noteRevision.content = await noteRevision.getContent(); | ||||
|  | ||||
|         if (noteRevision.content && noteRevision.type === 'image') { | ||||
|             noteRevision.content = noteRevision.content.toString('base64'); | ||||
|   | ||||
| @@ -13,17 +13,17 @@ async function getRecentChanges(req) { | ||||
|         SELECT * FROM ( | ||||
|             SELECT note_revisions.noteId, | ||||
|                    note_revisions.noteRevisionId, | ||||
|                    note_revisions.utcDateCreated AS date | ||||
|                    note_revisions.dateLastEdited AS date | ||||
|             FROM note_revisions | ||||
|             ORDER BY note_revisions.utcDateCreated DESC | ||||
|             ORDER BY note_revisions.dateLastEdited DESC | ||||
|         ) | ||||
|         UNION ALL SELECT * FROM ( | ||||
|             SELECT  | ||||
|                    notes.noteId, | ||||
|                    NULL AS noteRevisionId, | ||||
|                    utcDateModified AS date  | ||||
|                    dateModified AS date  | ||||
|             FROM notes  | ||||
|             ORDER BY utcDateModified DESC | ||||
|             ORDER BY dateModified DESC | ||||
|         ) | ||||
|         ORDER BY date DESC`); | ||||
|  | ||||
| @@ -44,7 +44,7 @@ async function getRecentChanges(req) { | ||||
|                     notes.title AS current_title, | ||||
|                     notes.isProtected AS current_isProtected, | ||||
|                     note_revisions.title, | ||||
|                     note_revisions.utcDateCreated AS date | ||||
|                     note_revisions.dateCreated AS date | ||||
|                 FROM  | ||||
|                     note_revisions | ||||
|                     JOIN notes USING(noteId) | ||||
| @@ -60,7 +60,7 @@ async function getRecentChanges(req) { | ||||
|                     notes.title AS current_title, | ||||
|                     notes.isProtected AS current_isProtected, | ||||
|                     notes.title, | ||||
|                     notes.utcDateModified AS date | ||||
|                     notes.dateModified AS date | ||||
|                 FROM | ||||
|                     notes | ||||
|                 WHERE noteId = ?`, [noteRow.noteId])); | ||||
|   | ||||
| @@ -11,7 +11,7 @@ const env = require('../services/env'); | ||||
| async function index(req, res) { | ||||
|     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(); | ||||
|     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"), | ||||
|         instanceName: config.General ? config.General.instanceName : null, | ||||
|         appCssNoteIds: await getAppCssNoteIds(), | ||||
|         isDev: env.isDev() | ||||
|         isDev: env.isDev(), | ||||
|         isMainWindow: !req.query.extra | ||||
|     }); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const setupRoute = require('./setup'); | ||||
| const loginRoute = require('./login'); | ||||
| const indexRoute = require('./index'); | ||||
| @@ -82,6 +84,7 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio | ||||
|         try { | ||||
|             const result = await cls.init(async () => { | ||||
|                 cls.namespace.set('sourceId', req.headers['trilium-source-id']); | ||||
|                 cls.namespace.set('localNowDateTime', req.headers['`trilium-local-now-datetime`']); | ||||
|                 protectedSessionService.setProtectedSessionId(req); | ||||
|  | ||||
|                 if (transactional) { | ||||
|   | ||||
| @@ -3,10 +3,11 @@ | ||||
| const sqlInit = require('../services/sql_init'); | ||||
| const setupService = require('../services/setup'); | ||||
| const utils = require('../services/utils'); | ||||
| const windowService = require('../services/window'); | ||||
|  | ||||
| async function setupPage(req, res) { | ||||
|     if (await sqlInit.isDbInitialized()) { | ||||
|         const windowService = require('../services/window'); | ||||
|  | ||||
|         if (utils.isElectron()) { | ||||
|             await windowService.createMainWindow(); | ||||
|             windowService.closeSetupWindow(); | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| module.exports = { buildDate:"2020-04-15T23:00:13+02:00", buildRevision: "dadcc93ae37fc03918837630c0a5a27a69af3495" }; | ||||
| module.exports = { buildDate:"2020-04-20T22:40:02+02:00", buildRevision: "a86177bb597c752fbc96a24d4be7ab5ae6c0344d" }; | ||||
|   | ||||
| @@ -12,7 +12,9 @@ const VIRTUAL_ATTRIBUTES = [ | ||||
|     "type", | ||||
|     "mime", | ||||
|     "text", | ||||
|     "parentCount" | ||||
|     "parentCount", | ||||
|     "attributeName", | ||||
|     "attributeValue" | ||||
| ]; | ||||
|  | ||||
| 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 | ||||
|             joins[alias] = `LEFT JOIN attributes AS ${alias} INDEXED BY IDX_attributes_noteId_index ` | ||||
|                 + `ON ${alias}.noteId = notes.noteId ` | ||||
|                 + `AND ${alias}.name = '${property}' AND ${alias}.isDeleted = 0`; | ||||
|                 + `ON ${alias}.noteId = notes.noteId AND ${alias}.isDeleted = 0 ` | ||||
|                 + `AND ${alias}.name = '${property}' `; | ||||
|  | ||||
|             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') { | ||||
|             const alias = "note_contents"; | ||||
|  | ||||
| @@ -73,79 +93,85 @@ module.exports = function(filters, selectedColumns = 'notes.*') { | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     let where = '1'; | ||||
|     const params = []; | ||||
|  | ||||
|     for (const filter of filters) { | ||||
|         if (['isarchived', 'in', 'orderby', 'limit'].includes(filter.name.toLowerCase())) { | ||||
|             continue; // these are not real filters | ||||
|         } | ||||
|     function parseWhereFilters(filters) { | ||||
|         let whereStmt = ''; | ||||
|  | ||||
|         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') { | ||||
|             where += `${accessor} IS NOT NULL`; | ||||
|         } | ||||
|         else if (filter.operator === 'not-exists') { | ||||
|             where += `${accessor} IS NULL`; | ||||
|         } | ||||
|         else if (filter.operator === '=' || filter.operator === '!=') { | ||||
|             where += `${accessor} ${filter.operator} ?`; | ||||
|             params.push(filter.value); | ||||
|         } | ||||
|         else if (filter.operator === '*=' || filter.operator === '!*=') { | ||||
|             where += `${accessor}` | ||||
|             if (filter.children) { | ||||
|                 whereStmt += "(" + parseWhereFilters(filter.children) + ")"; | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             const accessor = getAccessor(filter.name); | ||||
|  | ||||
|             if (filter.operator === 'exists') { | ||||
|                 whereStmt += `${accessor} IS NOT NULL`; | ||||
|             } else if (filter.operator === 'not-exists') { | ||||
|                 whereStmt += `${accessor} IS NULL`; | ||||
|             } 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' : '') | ||||
|                     + ` LIKE ` + utils.prepareSqlForLike('%', filter.value, ''); | ||||
|         } | ||||
|         else if (filter.operator === '=*' || filter.operator === '!=*') { | ||||
|             where += `${accessor}` | ||||
|             } else if (filter.operator === '=*' || filter.operator === '!=*') { | ||||
|                 whereStmt += `${accessor}` | ||||
|                     + (filter.operator.includes('!') ? ' NOT' : '') | ||||
|                     + ` LIKE ` + utils.prepareSqlForLike('', filter.value, '%'); | ||||
|         } | ||||
|         else if (filter.operator === '*=*' || filter.operator === '!*=*') { | ||||
|             const columns = filter.name === 'text' ? [getAccessor("title"), getAccessor("content")] : [accessor]; | ||||
|             } else if (filter.operator === '*=*' || filter.operator === '!*=*') { | ||||
|                 const columns = filter.name === 'text' ? [getAccessor("title"), getAccessor("content")] : [accessor]; | ||||
|  | ||||
|             let condition = "(" + columns.map(column => | ||||
|                 `${column}` + ` LIKE ` + utils.prepareSqlForLike('%', filter.value, '%')) | ||||
|                 .join(" OR ") + ")"; | ||||
|                 let condition = "(" + columns.map(column => | ||||
|                     `${column}` + ` LIKE ` + utils.prepareSqlForLike('%', filter.value, '%')) | ||||
|                     .join(" OR ") + ")"; | ||||
|  | ||||
|             if (filter.operator.includes('!')) { | ||||
|                 condition = "NOT(" + condition + ")"; | ||||
|             } | ||||
|                 if (filter.operator.includes('!')) { | ||||
|                     condition = "NOT(" + condition + ")"; | ||||
|                 } | ||||
|  | ||||
|             if (['text', 'title', 'content'].includes(filter.name)) { | ||||
|                 // for title/content search does not make sense to search for protected notes | ||||
|                 condition = `(${condition} AND notes.isProtected = 0)`; | ||||
|             } | ||||
|                 if (['text', 'title', 'content'].includes(filter.name)) { | ||||
|                     // for title/content search does not make sense to search for protected notes | ||||
|                     condition = `(${condition} AND notes.isProtected = 0)`; | ||||
|                 } | ||||
|  | ||||
|             where += condition; | ||||
|         } | ||||
|         else if ([">", ">=", "<", "<="].includes(filter.operator)) { | ||||
|             let floatParam; | ||||
|                 whereStmt += condition; | ||||
|             } else if ([">", ">=", "<", "<="].includes(filter.operator)) { | ||||
|                 let floatParam; | ||||
|  | ||||
|             // from https://stackoverflow.com/questions/12643009/regular-expression-for-floating-point-numbers | ||||
|             if (/^[+-]?([0-9]*[.])?[0-9]+$/.test(filter.value)) { | ||||
|                 floatParam = parseFloat(filter.value); | ||||
|             } | ||||
|                 // from https://stackoverflow.com/questions/12643009/regular-expression-for-floating-point-numbers | ||||
|                 if (/^[+-]?([0-9]*[.])?[0-9]+$/.test(filter.value)) { | ||||
|                     floatParam = parseFloat(filter.value); | ||||
|                 } | ||||
|  | ||||
|             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 | ||||
|                 where += `${accessor} ${filter.operator} ?`; | ||||
|                 params.push(filter.value); | ||||
|             } | ||||
|             else { | ||||
|                 where += `CAST(${accessor} AS DECIMAL) ${filter.operator} ?`; | ||||
|                 params.push(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 | ||||
|                     whereStmt += `${accessor} ${filter.operator} ?`; | ||||
|                     params.push(filter.value); | ||||
|                 } else { | ||||
|                     whereStmt += `CAST(${accessor} AS DECIMAL) ${filter.operator} ?`; | ||||
|                     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 no ordering is given then order at least by note title | ||||
|         orderBy.push("notes.title"); | ||||
|   | ||||
| @@ -13,6 +13,10 @@ function getSourceId() { | ||||
|     return namespace.get('sourceId'); | ||||
| } | ||||
|  | ||||
| function getLocalNowDateTime() { | ||||
|     return namespace.get('localNowDateTime'); | ||||
| } | ||||
|  | ||||
| function disableEntityEvents() { | ||||
|     namespace.set('disableEntityEvents', true); | ||||
| } | ||||
| @@ -50,6 +54,7 @@ module.exports = { | ||||
|     wrap, | ||||
|     namespace, | ||||
|     getSourceId, | ||||
|     getLocalNowDateTime, | ||||
|     disableEntityEvents, | ||||
|     isEntityEventsDisabled, | ||||
|     reset, | ||||
|   | ||||
| @@ -1,17 +1,29 @@ | ||||
| const dayjs = require('dayjs'); | ||||
| const cls = require('./cls'); | ||||
|  | ||||
| function utcNowDateTime() { | ||||
|     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() { | ||||
|     return dayjs().format('YYYY-MM-DD HH:mm:ss.SSSZZ') | ||||
|     return cls.getLocalNowDateTime() | ||||
|         || dayjs().format('YYYY-MM-DD HH:mm:ss.SSSZZ') | ||||
| } | ||||
|  | ||||
| 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) { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| module.exports = { | ||||
|     isDev: function () { | ||||
|         return process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === 'dev'; | ||||
|         return !!(process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === 'dev'); | ||||
|     } | ||||
| }; | ||||
| @@ -60,6 +60,20 @@ module.exports = function (searchText) { | ||||
|                 operator: '*=*', | ||||
|                 value: searchText | ||||
|             }); | ||||
|  | ||||
|             filters.push({ | ||||
|                 relation: 'or', | ||||
|                 name: 'attributeName', | ||||
|                 operator: '*=*', | ||||
|                 value: searchText | ||||
|             }); | ||||
|  | ||||
|             filters.push({ | ||||
|                 relation: 'or', | ||||
|                 name: 'attributeValue', | ||||
|                 operator: '*=*', | ||||
|                 value: searchText | ||||
|             }); | ||||
|         } | ||||
|         else { | ||||
|             const tokens = searchText.split(/\s+/); | ||||
| @@ -67,9 +81,27 @@ module.exports = function (searchText) { | ||||
|             for (const token of tokens) { | ||||
|                 filters.push({ | ||||
|                     relation: 'and', | ||||
|                     name: 'text', | ||||
|                     operator: '*=*', | ||||
|                     value: token | ||||
|                     name: 'sub', | ||||
|                     children: [ | ||||
|                         { | ||||
|                             relation: 'or', | ||||
|                             name: 'text', | ||||
|                             operator: '*=*', | ||||
|                             value: token | ||||
|                         }, | ||||
|                         { | ||||
|                             relation: 'or', | ||||
|                             name: 'attributeName', | ||||
|                             operator: '*=*', | ||||
|                             value: token | ||||
|                         }, | ||||
|                         { | ||||
|                             relation: 'or', | ||||
|                             name: 'attributeValue', | ||||
|                             operator: '*=*', | ||||
|                             value: token | ||||
|                         } | ||||
|                     ] | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -7,6 +7,7 @@ const log = require('./log'); | ||||
| const sqlInit = require('./sql_init'); | ||||
| const cls = require('./cls'); | ||||
| const keyboardActionsService = require('./keyboard_actions'); | ||||
| const {ipcMain} = require('electron'); | ||||
|  | ||||
| // Prevent window being garbage collected | ||||
| /** @type {Electron.BrowserWindow} */ | ||||
| @@ -14,6 +15,29 @@ let mainWindow; | ||||
| /** @type {Electron.BrowserWindow} */ | ||||
| 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() { | ||||
|     const windowStateKeeper = require('electron-window-state'); // should not be statically imported | ||||
|  | ||||
| @@ -68,7 +92,7 @@ async function createMainWindow() { | ||||
|  | ||||
|     if (spellcheckEnabled) { | ||||
|         const languageCodes = (await optionService.getOption('spellCheckLanguageCode')) | ||||
|             .split('/') | ||||
|             .split(',') | ||||
|             .map(code => code.trim()); | ||||
|  | ||||
|         webContents.session.setSpellCheckerLanguages(languageCodes); | ||||
| @@ -141,5 +165,6 @@ module.exports = { | ||||
|     createMainWindow, | ||||
|     createSetupWindow, | ||||
|     closeSetupWindow, | ||||
|     createExtraWindow, | ||||
|     registerGlobalShortcuts | ||||
| }; | ||||
| @@ -46,8 +46,9 @@ | ||||
|         maxSyncIdAtLoad: <%= maxSyncIdAtLoad %>, | ||||
|         instanceName: '<%= instanceName %>', | ||||
|         csrfToken: '<%= csrfToken %>', | ||||
|         isDev: '<%= isDev %>', | ||||
|         appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %> | ||||
|         isDev: <%= isDev %>, | ||||
|         appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %>, | ||||
|         isMainWindow: <%= isMainWindow %> | ||||
|     }; | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -18,7 +18,7 @@ | ||||
|                                 <ul> | ||||
|                                     <li><kbd>UP</kbd>, <kbd>DOWN</kbd> - go up/down in the list of notes</li> | ||||
|                                     <li><kbd>LEFT</kbd>, <kbd>RIGHT</kbd> - collapse/expand node</li> | ||||
|                                     <li><kbd data-command="backInNoteHistory"></kbd>, <kbd data-command="BackInNoteHistory"></kbd> - go back / forwards in the history</li> | ||||
|                                     <li><kbd data-command="backInNoteHistory"></kbd>, <kbd data-command="forwardInNoteHistory"></kbd> - go back / forwards in the history</li> | ||||
|                                     <li><kbd data-command="jumpToNote"></kbd> - show <a class="external" href="https://github.com/zadam/trilium/wiki/Note-navigation#jump-to-note">"Jump to" dialog</a></li> | ||||
|                                     <li><kbd data-command="scrollToActiveNote"></kbd> - scroll to active note</li> | ||||
|                                     <li><kbd data-command="activateParentNote"></kbd> - jumps to parent note</li> | ||||
| @@ -69,9 +69,9 @@ | ||||
|  | ||||
|                             <p class="card-text"> | ||||
|                                 <ul> | ||||
|                                     <li><kbd data-command="moveNoteUp"></kbd>, <kbd data-command="MoveNoteDown"></kbd> - move note up/down in the note list</li> | ||||
|                                     <li><kbd data-command="moveNoteUpInHierarchy"></kbd>, <kbd data-command="MoveNoteDownInHierarchy"></kbd> - move note up in the hierarchy</li> | ||||
|                                     <li><kbd data-command="addNoteAboveToSelection"></kbd>, <kbd data-command="AddNoteBelowToSelection"></kbd> - multi-select note above/below</li> | ||||
|                                     <li><kbd data-command="moveNoteUp"></kbd>, <kbd data-command="moveNoteDown"></kbd> - move note up/down in the note list</li> | ||||
|                                     <li><kbd data-command="moveNoteUpInHierarchy"></kbd>, <kbd data-command="moveNoteDownInHierarchy"></kbd> - move note up in the hierarchy</li> | ||||
|                                     <li><kbd data-command="addNoteAboveToSelection"></kbd>, <kbd data-command="addNoteBelowToSelection"></kbd> - multi-select note above/below</li> | ||||
|                                     <li><kbd data-command="selectAllNotesInParent"></kbd> - select all notes in the current level</li> | ||||
|                                     <li><kbd>Shift+click</kbd> - select note</li> | ||||
|                                     <li><kbd data-command="copyNotesToClipboard"></kbd> - copies active note (or current selection) into clipboard (used for <a class="external" href="https://github.com/zadam/trilium/wiki/Cloning notes">cloning</a>)</li> | ||||
| @@ -102,7 +102,7 @@ | ||||
|  | ||||
|                     <div class="card"> | ||||
|                         <div class="card-body"> | ||||
|                             <h5 class="card-title"><a class="external" href="https://github.com/zadam/trilium/wiki/Text-editor#autoformat">Markdown-like autoformatting</a></h5> | ||||
|                             <h5 class="card-title"><a class="external" href="https://github.com/zadam/trilium/wiki/Text-notes#autoformat">Markdown-like autoformatting</a></h5> | ||||
|  | ||||
|                             <p class="card-text"> | ||||
|                                 <ul> | ||||
|   | ||||
| @@ -29,7 +29,7 @@ | ||||
|                         <div id="note-revision-title-buttons"></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> | ||||
|   | ||||
| @@ -114,7 +114,7 @@ | ||||
|         maxSyncIdAtLoad: <%= maxSyncIdAtLoad %>, | ||||
|         instanceName: '<%= instanceName %>', | ||||
|         csrfToken: '<%= csrfToken %>', | ||||
|         isDev: '<%= isDev %>', | ||||
|         isDev: <%= isDev %>, | ||||
|         appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %> | ||||
|     }; | ||||
| </script> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user