mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	Compare commits
	
		
			41 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | a717ee00fb | ||
|  | f5e27278ab | ||
|  | 20c24e26cc | ||
|  | 3bafc396fc | ||
|  | 3fa3e912a4 | ||
|  | 44219e7ccc | ||
|  | 5b67854cbe | ||
|  | c6d912dcb7 | ||
|  | da53c1eaa8 | ||
|  | 73bf2dcb02 | ||
|  | 719f10981e | ||
|  | deb67d6275 | ||
|  | e4039ea5e1 | ||
|  | 78a50be663 | ||
|  | 3d3ad3b99b | ||
|  | 0d9cdcac85 | ||
|  | 350331e2ef | ||
|  | e8a9e49e9e | ||
|  | fb55cdaea6 | ||
|  | b9b2cc8364 | ||
|  | 8dfdd090f5 | ||
|  | fe7705524a | ||
|  | 2b1b7774f8 | ||
|  | 2d58019d6e | ||
|  | fe31f08c0d | ||
|  | ad7a55d305 | ||
|  | 4ce4ac9584 | ||
|  | 88bd65c679 | ||
|  | 9eab3026bb | ||
|  | 7abaedbf31 | ||
|  | 402718d293 | ||
|  | 990a84c202 | ||
|  | d8e181a828 | ||
|  | adb8caa8a2 | ||
|  | 7651c53363 | ||
|  | 0f25c8a95f | ||
|  | 1a49894adf | ||
|  | bd8c078fb9 | ||
|  | 6e060b87b8 | ||
|  | 2375b170ba | ||
|  | 9a13edd490 | 
							
								
								
									
										3
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							| @@ -6,9 +6,6 @@ | ||||
|       <synchronize>true</synchronize> | ||||
|       <jdbc-driver>org.sqlite.JDBC</jdbc-driver> | ||||
|       <jdbc-url>jdbc:sqlite:$PROJECT_DIR$/../trilium-data/document.db</jdbc-url> | ||||
|       <driver-properties> | ||||
|         <property name="enable_load_extension" value="true" /> | ||||
|       </driver-properties> | ||||
|     </data-source> | ||||
|   </component> | ||||
| </project> | ||||
| @@ -57,7 +57,6 @@ | ||||
|     <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"> | ||||
| @@ -131,21 +130,17 @@ | ||||
|     <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> | ||||
| @@ -212,17 +207,14 @@ 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> | ||||
| @@ -253,7 +245,6 @@ 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"> | ||||
| @@ -284,7 +275,6 @@ 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"> | ||||
| @@ -369,28 +359,22 @@ 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> | ||||
| @@ -477,36 +461,28 @@ 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> | ||||
| @@ -547,7 +523,6 @@ 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"> | ||||
| @@ -583,7 +558,6 @@ 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"> | ||||
| @@ -604,12 +578,10 @@ 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> | ||||
| @@ -663,22 +635,26 @@ parentNoteId</ColNames> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <column id="145" parent="18" name="utcSyncDate"> | ||||
|     <column id="145" parent="18" name="isSynced"> | ||||
|       <Position>5</Position> | ||||
|       <DataType>INTEGER|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|       <DefaultExpression>0</DefaultExpression> | ||||
|     </column> | ||||
|     <column id="146" parent="18" name="utcSyncDate"> | ||||
|       <Position>6</Position> | ||||
|       <DataType>TEXT|0s</DataType> | ||||
|       <NotNull>1</NotNull> | ||||
|     </column> | ||||
|     <index id="146" parent="18" name="IDX_sync_entityName_entityId"> | ||||
|     <index id="147" parent="18" name="IDX_sync_entityName_entityId"> | ||||
|       <ColNames>entityName | ||||
| entityId</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|       <Unique>1</Unique> | ||||
|     </index> | ||||
|     <index id="147" parent="18" name="IDX_sync_utcSyncDate"> | ||||
|     <index id="148" parent="18" name="IDX_sync_utcSyncDate"> | ||||
|       <ColNames>utcSyncDate</ColNames> | ||||
|       <ColumnCollations></ColumnCollations> | ||||
|     </index> | ||||
|     <key id="148" parent="18"> | ||||
|     <key id="149" parent="18"> | ||||
|       <ColNames>id</ColNames> | ||||
|       <Primary>1</Primary> | ||||
|     </key> | ||||
|   | ||||
							
								
								
									
										22
									
								
								db/migrations/0158__add_isSynced_to_sync.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								db/migrations/0158__add_isSynced_to_sync.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| CREATE TABLE IF NOT EXISTS "sync_mig" ( | ||||
|                                           `id`	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||
|                                           `entityName`	TEXT NOT NULL, | ||||
|                                           `entityId`	TEXT NOT NULL, | ||||
|                                           `sourceId` TEXT NOT NULL, | ||||
|                                           `isSynced` INTEGER default 0 not null, | ||||
|                                           `utcSyncDate`	TEXT NOT NULL); | ||||
|  | ||||
| INSERT INTO sync_mig (id, entityName, entityId, sourceId, isSynced, utcSyncDate) | ||||
| SELECT id, entityName, entityId, sourceId, 1, utcSyncDate FROM sync; | ||||
|  | ||||
| DROP TABLE sync; | ||||
|  | ||||
| ALTER TABLE sync_mig RENAME TO sync; | ||||
|  | ||||
| CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` ( | ||||
|                                                               `entityName`, | ||||
|                                                               `entityId` | ||||
|     ); | ||||
| CREATE INDEX `IDX_sync_utcSyncDate` ON `sync` ( | ||||
|                                                `utcSyncDate` | ||||
|     ); | ||||
| @@ -1,16 +1,8 @@ | ||||
| CREATE TABLE IF NOT EXISTS "sync" ( | ||||
|                                     `id`	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||
|                                     `entityName`	TEXT NOT NULL, | ||||
|                                     `entityId`	TEXT NOT NULL, | ||||
|                                     `sourceId` TEXT NOT NULL, | ||||
|                                     `utcSyncDate`	TEXT NOT NULL); | ||||
| CREATE TABLE IF NOT EXISTS "source_ids" ( | ||||
|                                           `sourceId`	TEXT NOT NULL, | ||||
|                                           `utcDateCreated`	TEXT NOT NULL, | ||||
|                                           PRIMARY KEY(`sourceId`) | ||||
| ); | ||||
| CREATE INDEX IDX_source_ids_utcDateCreated | ||||
|     on source_ids (utcDateCreated); | ||||
| CREATE TABLE IF NOT EXISTS "api_tokens" | ||||
| ( | ||||
|   apiTokenId TEXT PRIMARY KEY NOT NULL, | ||||
| @@ -27,13 +19,6 @@ CREATE TABLE IF NOT EXISTS "options" | ||||
|   utcDateCreated TEXT not null, | ||||
|   utcDateModified TEXT NOT NULL | ||||
| ); | ||||
| CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` ( | ||||
|                                                               `entityName`, | ||||
|                                                               `entityId` | ||||
|   ); | ||||
| CREATE INDEX `IDX_sync_utcSyncDate` ON `sync` ( | ||||
|                                             `utcSyncDate` | ||||
|   ); | ||||
| CREATE TABLE IF NOT EXISTS "note_contents" ( | ||||
|                                                    `noteId`	TEXT NOT NULL, | ||||
|                                                    `content`	TEXT NULL DEFAULT NULL, | ||||
| @@ -72,6 +57,8 @@ CREATE INDEX `IDX_note_revisions_utcDateCreated` ON `note_revisions` (`utcDateCr | ||||
| CREATE INDEX `IDX_note_revisions_utcDateLastEdited` ON `note_revisions` (`utcDateLastEdited`); | ||||
| CREATE INDEX `IDX_note_revisions_dateCreated` ON `note_revisions` (`dateCreated`); | ||||
| CREATE INDEX `IDX_note_revisions_dateLastEdited` ON `note_revisions` (`dateLastEdited`); | ||||
| CREATE INDEX IDX_source_ids_utcDateCreated | ||||
|     on source_ids (utcDateCreated); | ||||
| CREATE TABLE IF NOT EXISTS "notes" ( | ||||
|                                            `noteId`	TEXT NOT NULL, | ||||
|                                            `title`	TEXT NOT NULL DEFAULT "note", | ||||
| @@ -130,3 +117,17 @@ CREATE INDEX IDX_attributes_noteId_index | ||||
|     on attributes (noteId); | ||||
| CREATE INDEX IDX_attributes_value_index | ||||
|     on attributes (value); | ||||
| CREATE TABLE IF NOT EXISTS "sync" ( | ||||
|     `id`	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||
|     `entityName`	TEXT NOT NULL, | ||||
|     `entityId`	TEXT NOT NULL, | ||||
|     `sourceId` TEXT NOT NULL, | ||||
|     `isSynced` INTEGER default 0 not null, | ||||
|     `utcSyncDate`	TEXT NOT NULL); | ||||
| CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` ( | ||||
|                                                               `entityName`, | ||||
|                                                               `entityId` | ||||
|     ); | ||||
| CREATE INDEX `IDX_sync_utcSyncDate` ON `sync` ( | ||||
|                                                `utcSyncDate` | ||||
|     ); | ||||
|   | ||||
| @@ -26,9 +26,9 @@ app.on('ready', async () => { | ||||
|  | ||||
|     await sqlInit.dbConnection; | ||||
|  | ||||
|     // if schema doesn't exist -> setup process | ||||
|     // if schema exists, then we need to wait until the migration process is finished | ||||
|     if (await sqlInit.schemaExists()) { | ||||
|     // if db is not initialized -> setup process | ||||
|     // if db is initialized, then we need to wait until the migration process is finished | ||||
|     if (await sqlInit.isDbInitialized()) { | ||||
|         await sqlInit.dbReady; | ||||
|  | ||||
|         await windowService.createMainWindow(); | ||||
|   | ||||
							
								
								
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "trilium", | ||||
|   "version": "0.40.0-beta", | ||||
|   "version": "0.40.4", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|   "name": "trilium", | ||||
|   "productName": "Trilium Notes", | ||||
|   "description": "Trilium Notes", | ||||
|   "version": "0.40.1", | ||||
|   "version": "0.40.6", | ||||
|   "license": "AGPL-3.0-only", | ||||
|   "main": "electron.js", | ||||
|   "bin": { | ||||
|   | ||||
| @@ -11,7 +11,9 @@ const ENTITY_NAME_TO_ENTITY = { | ||||
|     "attributes": Attribute, | ||||
|     "branches": Branch, | ||||
|     "notes": Note, | ||||
|     "note_contents": Note, | ||||
|     "note_revisions": NoteRevision, | ||||
|     "note_revision_contents": NoteRevision, | ||||
|     "recent_notes": RecentNote, | ||||
|     "options": Option, | ||||
|     "api_tokens": ApiToken, | ||||
|   | ||||
| @@ -811,8 +811,10 @@ class Note extends Entity { | ||||
|             FROM attributes  | ||||
|             WHERE noteId = ? AND  | ||||
|                   isDeleted = 0 AND  | ||||
|                   type = 'relation' AND  | ||||
|                   name IN ('internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink')`, [this.noteId]); | ||||
|                   ((type = 'relation' AND  | ||||
|                     name IN ('internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink')) | ||||
|                   OR | ||||
|                    (type = 'label' AND name = 'externalLink'))`, [this.noteId]); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -161,7 +161,13 @@ async function printActiveNote() { | ||||
|         importCSS: false, | ||||
|         loadCSS: [ | ||||
|             "libraries/codemirror/codemirror.css", | ||||
|             "libraries/ckeditor/ckeditor-content.css" | ||||
|             "libraries/ckeditor/ckeditor-content.css", | ||||
|             "libraries/ckeditor/ckeditor-content.css", | ||||
|             "libraries/bootstrap/css/bootstrap.min.css", | ||||
|             "stylesheets/print.css", | ||||
|             "stylesheets/relation_map.css", | ||||
|             "stylesheets/themes.css", | ||||
|             "stylesheets/detail.css" | ||||
|         ], | ||||
|         debug: true | ||||
|     }); | ||||
|   | ||||
| @@ -10,8 +10,6 @@ const $buildRevision = $("#build-revision"); | ||||
| const $dataDirectory = $("#data-directory"); | ||||
|  | ||||
| export async function showDialog() { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     const appInfo = await server.get('app-info'); | ||||
|  | ||||
|     $appVersion.text(appInfo.appVersion); | ||||
| @@ -22,7 +20,5 @@ export async function showDialog() { | ||||
|     $buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision); | ||||
|     $dataDirectory.text(appInfo.dataDirectory); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
| } | ||||
| @@ -11,13 +11,9 @@ const $linkTitle = $("#link-title"); | ||||
| const $addLinkTitleFormGroup = $("#add-link-title-form-group"); | ||||
|  | ||||
| export async function showDialog() { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     $addLinkTitleFormGroup.toggle(!hasSelection()); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     $autoComplete.val('').trigger('focus'); | ||||
|     $linkTitle.val(''); | ||||
|   | ||||
| @@ -287,8 +287,6 @@ function initKoPlugins() { | ||||
| } | ||||
|  | ||||
| export async function showDialog() { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     await libraryLoader.requireLibrary(libraryLoader.KNOCKOUT); | ||||
|  | ||||
|     // lazily apply bindings on first use | ||||
| @@ -300,11 +298,9 @@ export async function showDialog() { | ||||
|         ko.applyBindings(attributesModel, $dialog[0]); | ||||
|     } | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     await attributesModel.loadAttributes(); | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
| } | ||||
|  | ||||
| $dialog.on('focus', '.attribute-name', function (e) { | ||||
|   | ||||
| @@ -6,11 +6,7 @@ const $backendLogTextArea = $("#backend-log-textarea"); | ||||
| const $refreshBackendLog = $("#refresh-backend-log-button"); | ||||
|  | ||||
| export async function showDialog() { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     load(); | ||||
| } | ||||
|   | ||||
| @@ -13,10 +13,6 @@ const $noteTitle = $('#branch-prefix-note-title'); | ||||
| let branchId; | ||||
|  | ||||
| export async function showDialog(node) { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     branchId = node.data.branchId; | ||||
|     const branch = treeCache.getBranch(branchId); | ||||
|  | ||||
| @@ -30,7 +26,7 @@ export async function showDialog(node) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     $treePrefixInput.val(branch.prefix); | ||||
|  | ||||
|   | ||||
| @@ -22,11 +22,7 @@ export async function showDialog(noteIds) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     $noteAutoComplete.val('').trigger('focus'); | ||||
|  | ||||
|   | ||||
| @@ -17,8 +17,6 @@ let taskId = ''; | ||||
| let branchId = null; | ||||
|  | ||||
| export async function showDialog(node, defaultType) { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     // each opening of the dialog resets the taskId so we don't associate it with previous exports anymore | ||||
|     taskId = ''; | ||||
|     $exportButton.removeAttr("disabled"); | ||||
| @@ -38,9 +36,7 @@ export async function showDialog(node, defaultType) { | ||||
|  | ||||
|     $("#opml-v2").prop("checked", true); // setting default | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     branchId = node.data.branchId; | ||||
|  | ||||
|   | ||||
| @@ -3,9 +3,5 @@ import utils from "../services/utils.js"; | ||||
| const $dialog = $("#help-dialog"); | ||||
|  | ||||
| export async function showDialog() { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
| } | ||||
| @@ -16,8 +16,6 @@ const $explodeArchivesCheckbox = $("#explode-archives-checkbox"); | ||||
| let parentNoteId = null; | ||||
|  | ||||
| export async function showDialog(node) { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     $fileUploadInput.val('').trigger('change'); // to trigger Import button disabling listener below | ||||
|  | ||||
|     $safeImportCheckbox.prop("checked", true); | ||||
| @@ -26,13 +24,11 @@ export async function showDialog(node) { | ||||
|     $codeImportedAsCodeCheckbox.prop("checked", true); | ||||
|     $explodeArchivesCheckbox.prop("checked", true); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     parentNoteId = node.data.noteId; | ||||
|  | ||||
|     $noteTitle.text(await treeUtils.getNoteTitle(parentNoteId)); | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
| } | ||||
|  | ||||
| $form.on('submit', () => { | ||||
|   | ||||
| @@ -10,13 +10,9 @@ let callback = null; | ||||
| export async function showDialog(cb) { | ||||
|     callback = cb; | ||||
|  | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     $autoComplete.val(''); | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     noteAutocompleteService.initNoteAutocomplete($autoComplete, { hideGoToSelectedNoteButton: true }); | ||||
|     noteAutocompleteService.showRecentNotes($autoComplete); | ||||
|   | ||||
| @@ -10,13 +10,9 @@ let $originallyFocused; // element focused before the dialog was opened so we ca | ||||
| export function info(message) { | ||||
|     $originallyFocused = $(':focus'); | ||||
|  | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     $infoContent.text(message); | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     return new Promise((res, rej) => { resolve = res; }); | ||||
| } | ||||
|   | ||||
| @@ -8,13 +8,9 @@ const $autoComplete = $("#jump-to-note-autocomplete"); | ||||
| const $showInFullTextButton = $("#show-in-full-text-button"); | ||||
|  | ||||
| export async function showDialog() { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     $autoComplete.val(''); | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     noteAutocompleteService.initNoteAutocomplete($autoComplete, { hideGoToSelectedNoteButton: true }) | ||||
|         .on('autocomplete:selected', function(event, suggestion, dataset) { | ||||
|   | ||||
| @@ -16,10 +16,6 @@ function getOptions() { | ||||
| } | ||||
|  | ||||
| export async function showDialog() { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     // set default settings | ||||
|     $maxNotesInput.val(20); | ||||
|  | ||||
| @@ -27,7 +23,7 @@ export async function showDialog() { | ||||
|  | ||||
|     $linkMapContainer.empty(); | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
| } | ||||
|  | ||||
| $dialog.on('shown.bs.modal', () => { | ||||
|   | ||||
| @@ -37,9 +37,7 @@ export async function importMarkdownInline() { | ||||
|         convertMarkdownToHtml(text); | ||||
|     } | ||||
|     else { | ||||
|         glob.activeDialog = $dialog; | ||||
|  | ||||
|         $dialog.modal(); | ||||
|         utils.openDialog($dialog); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,5 @@ | ||||
| import noteAutocompleteService from "../services/note_autocomplete.js"; | ||||
| import utils from "../services/utils.js"; | ||||
| import cloningService from "../services/cloning.js"; | ||||
| import treeUtils from "../services/tree_utils.js"; | ||||
| import toastService from "../services/toast.js"; | ||||
| import treeCache from "../services/tree_cache.js"; | ||||
| import treeChangesService from "../services/branches.js"; | ||||
| @@ -18,11 +16,7 @@ let movedNodes; | ||||
| export async function showDialog(nodes) { | ||||
|     movedNodes = nodes; | ||||
|  | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     $noteAutoComplete.val('').trigger('focus'); | ||||
|  | ||||
|   | ||||
| @@ -10,11 +10,7 @@ const $mime = $("#note-info-mime"); | ||||
| const $okButton = $("#note-info-ok-button"); | ||||
|  | ||||
| export function showDialog() { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     const activeNote = noteDetailService.getActiveTabNote(); | ||||
|  | ||||
|   | ||||
| @@ -29,11 +29,7 @@ export async function showCurrentNoteRevisions() { | ||||
| } | ||||
|  | ||||
| export async function showNoteRevisionsDialog(noteId, noteRevisionId) { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     await loadNoteRevisions(noteId, noteRevisionId); | ||||
| } | ||||
|   | ||||
| @@ -5,11 +5,7 @@ const $dialog = $("#note-source-dialog"); | ||||
| const $noteSource = $("#note-source"); | ||||
|  | ||||
| export function showDialog() { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     const noteText = noteDetailService.getActiveTabNote().content; | ||||
|  | ||||
|   | ||||
| @@ -6,13 +6,9 @@ import utils from "../services/utils.js"; | ||||
| const $dialog = $("#options-dialog"); | ||||
|  | ||||
| export async function showDialog() { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     const options = await server.get('options'); | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     (await Promise.all([ | ||||
|         import('./options/advanced.js'), | ||||
|   | ||||
| @@ -12,10 +12,6 @@ let resolve; | ||||
| let shownCb; | ||||
|  | ||||
| export function ask({ message, defaultValue, shown }) { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     shownCb = shown; | ||||
|  | ||||
|     $question = $("<label>") | ||||
| @@ -34,7 +30,7 @@ export function ask({ message, defaultValue, shown }) { | ||||
|             .append($question) | ||||
|             .append($answer)); | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     return new Promise((res, rej) => { resolve = res; }); | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,12 @@ | ||||
| import protectedSessionService from "../services/protected_session.js"; | ||||
| import utils from "../services/utils.js"; | ||||
|  | ||||
| const $dialog = $("#protected-session-password-dialog"); | ||||
| const $passwordForm = $dialog.find(".protected-session-password-form"); | ||||
| const $passwordInput = $dialog.find(".protected-session-password"); | ||||
|  | ||||
| export function show() { | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     $passwordInput.trigger('focus'); | ||||
| } | ||||
|   | ||||
| @@ -8,11 +8,7 @@ const $dialog = $("#recent-changes-dialog"); | ||||
| const $content = $("#recent-changes-content"); | ||||
|  | ||||
| export async function showDialog() { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
|  | ||||
|     const result = await server.get('recent-changes'); | ||||
|  | ||||
|   | ||||
| @@ -14,13 +14,9 @@ let codeEditor; | ||||
| $dialog.on("shown.bs.modal", e => initEditor()); | ||||
|  | ||||
| export async function showDialog() { | ||||
|     utils.closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     await showTableSchemas(); | ||||
|  | ||||
|     $dialog.modal(); | ||||
|     utils.openDialog($dialog); | ||||
| } | ||||
|  | ||||
| async function initEditor() { | ||||
|   | ||||
| @@ -7,9 +7,26 @@ import contextMenuWidget from "./services/context_menu.js"; | ||||
| import treeChangesService from "./services/branches.js"; | ||||
| import utils from "./services/utils.js"; | ||||
| import treeUtils from "./services/tree_utils.js"; | ||||
| import linkService from "./services/link.js"; | ||||
| import noteContentRenderer from "./services/note_content_renderer.js"; | ||||
|  | ||||
| window.glob.isDesktop = utils.isDesktop; | ||||
| window.glob.isMobile = utils.isMobile; | ||||
| window.glob.showAddLinkDialog = () => import('./dialogs/add_link.js').then(d => d.showDialog()); | ||||
| window.glob.showIncludeNoteDialog = cb => import('./dialogs/include_note.js').then(d => d.showDialog(cb)); | ||||
| window.glob.loadIncludedNote = async (noteId, el) => { | ||||
|     const note = await treeCache.getNote(noteId); | ||||
|  | ||||
|     if (note) { | ||||
|         $(el).empty().append($("<h3>").append(await linkService.createNoteLink(note.noteId, { | ||||
|             showTooltip: false | ||||
|         }))); | ||||
|  | ||||
|         const {renderedContent} = await noteContentRenderer.getRenderedContent(note); | ||||
|  | ||||
|         $(el).append(renderedContent); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| const $leftPane = $("#left-pane"); | ||||
| const $tree = $("#tree"); | ||||
| @@ -87,6 +104,8 @@ async function showTree() { | ||||
|             }); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     treeService.setTree($.ui.fancytree.getTree("#tree")); | ||||
| } | ||||
|  | ||||
| $detail.on("click", ".note-menu-button", async e => { | ||||
|   | ||||
| @@ -7,31 +7,20 @@ import protectedSessionHolder from "./protected_session_holder.js"; | ||||
| async function getRenderedContent(note) { | ||||
|     const type = getRenderingType(note); | ||||
|  | ||||
|     let rendered; | ||||
|     let $rendered; | ||||
|  | ||||
|     if (type === 'text') { | ||||
|         const fullNote = await server.get('notes/' + note.noteId); | ||||
|  | ||||
|         const $content = $("<div>").html(fullNote.content); | ||||
|  | ||||
|         if (utils.isHtmlEmpty(fullNote.content)) { | ||||
|             rendered = ""; | ||||
|         } | ||||
|         else { | ||||
|             rendered = $content; | ||||
|         } | ||||
|         $rendered = $("<div>").html(fullNote.content); | ||||
|     } | ||||
|     else if (type === 'code') { | ||||
|         const fullNote = await server.get('notes/' + note.noteId); | ||||
|  | ||||
|         if (fullNote.content.trim() === "") { | ||||
|             rendered = ""; | ||||
|         } | ||||
|  | ||||
|         rendered = $("<pre>").text(fullNote.content); | ||||
|         $rendered = $("<pre>").text(fullNote.content); | ||||
|     } | ||||
|     else if (type === 'image') { | ||||
|         rendered = $("<img>").attr("src", `api/images/${note.noteId}/${note.title}`); | ||||
|         $rendered = $("<img>").attr("src", `api/images/${note.noteId}/${note.title}`); | ||||
|     } | ||||
|     else if (type === 'file') { | ||||
|         function getFileUrl() { | ||||
| @@ -56,33 +45,35 @@ async function getRenderedContent(note) { | ||||
|         // open doesn't work for protected notes since it works through browser which isn't in protected session | ||||
|         $openButton.toggle(!note.isProtected); | ||||
|  | ||||
|         rendered = $('<div>') | ||||
|         $rendered = $('<div>') | ||||
|             .append($downloadButton) | ||||
|             .append('   ') | ||||
|             .append($openButton); | ||||
|     } | ||||
|     else if (type === 'render') { | ||||
|         const $el = $('<div>'); | ||||
|         $rendered = $('<div>'); | ||||
|  | ||||
|         await renderService.render(note, $el, this.ctx); | ||||
|  | ||||
|         rendered = $el; | ||||
|         await renderService.render(note, $rendered, this.ctx); | ||||
|     } | ||||
|     else if (type === 'protected-session') { | ||||
|         const $button = $(`<button class="btn btn-sm"><span class="bx bx-log-in"></span> Enter protected session</button>`) | ||||
|             .on('click', protectedSessionService.enterProtectedSession); | ||||
|  | ||||
|         rendered = $("<div>") | ||||
|         $rendered = $("<div>") | ||||
|             .append("<div>This note is protected and to access it you need to enter password.</div>") | ||||
|             .append("<br/>") | ||||
|             .append($button); | ||||
|     } | ||||
|     else { | ||||
|         rendered = "<em>Content of this note cannot be displayed in the book format</em>"; | ||||
|         $rendered = $("<em>Content of this note cannot be displayed in the book format</em>"); | ||||
|     } | ||||
|  | ||||
|     if (note.cssClass) { | ||||
|         $rendered.addClass(note.cssClass); | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|         renderedContent: rendered, | ||||
|         renderedContent: $rendered, | ||||
|         type | ||||
|     }; | ||||
| } | ||||
|   | ||||
| @@ -148,8 +148,8 @@ class NoteDetailBook { | ||||
|                 const label = `${childCount} child${childCount > 1 ? 'ren' : ''}`; | ||||
|  | ||||
|                 $card.append($('<div class="note-book-children">') | ||||
|                     .append($(`<a class="note-book-open-children-button" href="javascript:">+ Show ${label}</a>`)) | ||||
|                     .append($(`<a class="note-book-hide-children-button" href="javascript:">- Hide ${label}</a>`).hide()) | ||||
|                     .append($(`<a class="note-book-open-children-button no-print" href="javascript:">+ Show ${label}</a>`)) | ||||
|                     .append($(`<a class="note-book-hide-children-button no-print" href="javascript:">- Hide ${label}</a>`).hide()) | ||||
|                     .append($('<div class="note-book-children-content">')) | ||||
|                 ); | ||||
|             } | ||||
|   | ||||
| @@ -60,6 +60,7 @@ class NoteDetailCode { | ||||
|             // CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check) | ||||
|             // we provide fallback | ||||
|             this.codeEditor.setValue(this.ctx.note.content || ""); | ||||
|             this.codeEditor.clearHistory(); | ||||
|  | ||||
|             const info = CodeMirror.findModeByMIME(this.ctx.note.mime); | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import treeService from './tree.js'; | ||||
| import treeCache from "./tree_cache.js"; | ||||
| import server from './server.js'; | ||||
| import toastService from "./toast.js"; | ||||
| import utils from "./utils.js"; | ||||
|  | ||||
| const $searchInput = $("input[name='search-text']"); | ||||
| const $resetSearchButton = $("#reset-search-button"); | ||||
| @@ -28,6 +29,8 @@ const helpText = ` | ||||
| </p>`; | ||||
|  | ||||
| function showSearch() { | ||||
|     utils.saveFocusedElement(); | ||||
|  | ||||
|     $searchBox.slideDown(); | ||||
|  | ||||
|     $searchBox.tooltip({ | ||||
| @@ -49,6 +52,8 @@ function hideSearch() { | ||||
|  | ||||
|     $searchResults.hide(); | ||||
|     $searchBox.slideUp(); | ||||
|  | ||||
|     utils.focusSavedElement(); | ||||
| } | ||||
|  | ||||
| function toggleSearch() { | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import splitService from "./split.js"; | ||||
| import optionService from "./options.js"; | ||||
| import server from "./server.js"; | ||||
| import noteDetailService from "./note_detail.js"; | ||||
| import utils from "./utils.js"; | ||||
|  | ||||
| const $sidebar = $("#right-pane"); | ||||
| const $sidebarContainer = $('#sidebar-container'); | ||||
| @@ -15,6 +16,10 @@ const $hideSidebarButton = $("#hide-sidebar-button"); | ||||
| optionService.waitForOptions().then(options => toggleSidebar(options.is('rightPaneVisible'))); | ||||
|  | ||||
| function toggleSidebar(show) { | ||||
|     if (utils.isMobile()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     $sidebar.toggle(show); | ||||
|     $showSidebarButton.toggle(!show); | ||||
|     $hideSidebarButton.toggle(show); | ||||
|   | ||||
| @@ -303,7 +303,11 @@ class TabContext { | ||||
|  | ||||
|         let type = this.note.type; | ||||
|  | ||||
|         if (type === 'text' && !disableAutoBook && utils.isHtmlEmpty(this.note.content) && this.note.hasChildren()) { | ||||
|         if (type === 'text' | ||||
|             && !disableAutoBook | ||||
|             && utils.isHtmlEmpty(this.note.content) | ||||
|             && this.note.hasChildren() | ||||
|             && utils.isDesktop()) { | ||||
|             type = 'book'; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -635,7 +635,7 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) { | ||||
|         extraOptions.saveSelection = false; | ||||
|     } | ||||
|  | ||||
|     if (extraOptions.saveSelection) { | ||||
|     if (extraOptions.saveSelection && utils.isCKEditorInitialized()) { | ||||
|         [extraOptions.title, extraOptions.content] = parseSelectedHtml(window.cutToNote.getSelectedHtml()); | ||||
|     } | ||||
|  | ||||
| @@ -648,7 +648,7 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) { | ||||
|         type: extraOptions.type | ||||
|     }); | ||||
|  | ||||
|     if (extraOptions.saveSelection) { | ||||
|     if (extraOptions.saveSelection && utils.isCKEditorInitialized()) { | ||||
|         // we remove the selection only after it was saved to server to make sure we don't lose anything | ||||
|         window.cutToNote.removeSelection(); | ||||
|     } | ||||
| @@ -870,7 +870,7 @@ window.glob.cutIntoNote = () => createNoteInto(true); | ||||
|  | ||||
| keyboardActionService.setGlobalActionHandler('CutIntoNote', () => createNoteInto(true)); | ||||
|  | ||||
| keyboardActionService.setGlobalActionHandler('CreateNoteInto', createNoteInto); | ||||
| keyboardActionService.setGlobalActionHandler('CreateNoteInto', () => createNoteInto(true)); | ||||
|  | ||||
| keyboardActionService.setGlobalActionHandler('ScrollToActiveNote', scrollToActiveNote); | ||||
|  | ||||
| @@ -913,6 +913,10 @@ function getNodeByKey(key) { | ||||
|     return tree.getNodeByKey(key); | ||||
| } | ||||
|  | ||||
| function setTree(treeInstance) { | ||||
|     tree = treeInstance; | ||||
| } | ||||
|  | ||||
| keyboardActionService.setGlobalActionHandler('CollapseTree', () => collapseTree()); // don't use shortened form since collapseTree() accepts argument | ||||
| $collapseTreeButton.on('click', () => collapseTree()); | ||||
|  | ||||
| @@ -949,5 +953,6 @@ export default { | ||||
|     focusTree, | ||||
|     scrollToActiveNote, | ||||
|     duplicateNote, | ||||
|     getNodeByKey | ||||
|     getNodeByKey, | ||||
|     setTree | ||||
| }; | ||||
| @@ -62,6 +62,11 @@ class TreeContextMenu { | ||||
|             !isHoisted || !isNotRoot ? null : { title: 'Unhoist note <kbd data-kb-action="ToggleNoteHoisting"></kbd>', cmd: "unhoist", uiIcon: "arrow-up" }, | ||||
|             { title: 'Edit branch prefix <kbd data-kb-action="EditBranchPrefix"></kbd>', cmd: "editBranchPrefix", uiIcon: "empty", | ||||
|                 enabled: isNotRoot && parentNotSearch && noSelectedNotes}, | ||||
|             { title: "Advanced", uiIcon: "empty", enabled: true, items: [ | ||||
|                     { title: 'Collapse subtree <kbd data-kb-action="CollapseSubtree"></kbd>', cmd: "collapseSubtree", uiIcon: "align-justify", enabled: noSelectedNotes }, | ||||
|                     { title: "Force note sync", cmd: "forceNoteSync", uiIcon: "recycle", enabled: noSelectedNotes }, | ||||
|                     { title: 'Sort alphabetically <kbd data-kb-action="SortChildNotes"></kbd>', cmd: "sortAlphabetically", uiIcon: "empty", enabled: noSelectedNotes && notSearch } | ||||
|                 ] }, | ||||
|             { title: "----" }, | ||||
|             { title: "Protect subtree", cmd: "protectSubtree", uiIcon: "check-shield", enabled: noSelectedNotes }, | ||||
|             { title: "Unprotect subtree", cmd: "unprotectSubtree", uiIcon: "shield", enabled: noSelectedNotes }, | ||||
| @@ -84,12 +89,7 @@ class TreeContextMenu { | ||||
|             { title: "Export", cmd: "export", uiIcon: "empty", | ||||
|                 enabled: notSearch && noSelectedNotes }, | ||||
|             { title: "Import into note", cmd: "importIntoNote", uiIcon: "empty", | ||||
|                 enabled: notSearch && noSelectedNotes }, | ||||
|             { title: "Advanced", uiIcon: "empty", enabled: true, items: [ | ||||
|                     { title: 'Collapse subtree <kbd data-kb-action="CollapseSubtree"></kbd>', cmd: "collapseSubtree", uiIcon: "align-justify", enabled: noSelectedNotes }, | ||||
|                     { title: "Force note sync", cmd: "forceNoteSync", uiIcon: "recycle", enabled: noSelectedNotes }, | ||||
|                     { title: 'Sort alphabetically <kbd data-kb-action="SortChildNotes"></kbd>', cmd: "sortAlphabetically", uiIcon: "empty", enabled: noSelectedNotes && notSearch } | ||||
|                 ] }, | ||||
|                 enabled: notSearch && noSelectedNotes } | ||||
|         ].filter(row => row !== null); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -209,9 +209,50 @@ function getMimeTypeClass(mime) { | ||||
| function closeActiveDialog() { | ||||
|     if (glob.activeDialog) { | ||||
|         glob.activeDialog.modal('hide'); | ||||
|         glob.activeDialog = null; | ||||
|     } | ||||
| } | ||||
|  | ||||
| let $lastFocusedElement = null; | ||||
|  | ||||
| function saveFocusedElement() { | ||||
|     $lastFocusedElement = $(":focus"); | ||||
| } | ||||
|  | ||||
| function focusSavedElement() { | ||||
|     if (!$lastFocusedElement) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if ($lastFocusedElement.hasClass("ck")) { | ||||
|         // must handle CKEditor separately because of this bug: https://github.com/ckeditor/ckeditor5/issues/607 | ||||
|  | ||||
|         import("./note_detail.js").then(noteDetail => { | ||||
|             noteDetail.default.getActiveEditor().editing.view.focus(); | ||||
|         }); | ||||
|     } else { | ||||
|         $lastFocusedElement.focus(); | ||||
|     } | ||||
|  | ||||
|     $lastFocusedElement = null; | ||||
| } | ||||
|  | ||||
| function openDialog($dialog) { | ||||
|     closeActiveDialog(); | ||||
|  | ||||
|     glob.activeDialog = $dialog; | ||||
|  | ||||
|     saveFocusedElement(); | ||||
|  | ||||
|     $dialog.modal(); | ||||
|  | ||||
|     $dialog.on('hidden.bs.modal', () => { | ||||
|         if (!glob.activeDialog || glob.activeDialog === $dialog) { | ||||
|             focusSavedElement(); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function isHtmlEmpty(html) { | ||||
|     html = html.toLowerCase(); | ||||
|  | ||||
| @@ -248,6 +289,10 @@ function copySelectionToClipboard() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| function isCKEditorInitialized() { | ||||
|     return !!(window && window.cutToNote); | ||||
| } | ||||
|  | ||||
| export default { | ||||
|     reloadApp, | ||||
|     parseDate, | ||||
| @@ -277,9 +322,13 @@ export default { | ||||
|     getNoteTypeClass, | ||||
|     getMimeTypeClass, | ||||
|     closeActiveDialog, | ||||
|     openDialog, | ||||
|     saveFocusedElement, | ||||
|     focusSavedElement, | ||||
|     isHtmlEmpty, | ||||
|     clearBrowserCache, | ||||
|     getUrlForDownload, | ||||
|     normalizeShortcut, | ||||
|     copySelectionToClipboard | ||||
|     copySelectionToClipboard, | ||||
|     isCKEditorInitialized | ||||
| }; | ||||
| @@ -71,7 +71,19 @@ class AttributesWidget extends StandardWidget { | ||||
|     async renderAttributes(attributes, $container) { | ||||
|         for (const attribute of attributes) { | ||||
|             if (attribute.type === 'label') { | ||||
|                 $container.append(utils.formatLabel(attribute) + " "); | ||||
|                 if (attribute.name === 'externalLink') { | ||||
|                     $container.append('@' + attribute.name + "="); | ||||
|                     $container.append( | ||||
|                         $('<a>') | ||||
|                             .text(attribute.value) | ||||
|                             .attr('href', attribute.value) | ||||
|                             .addClass('external') | ||||
|                     ); | ||||
|                     $container.append(" "); | ||||
|                 } | ||||
|                 else { | ||||
|                     $container.append(utils.formatLabel(attribute) + " "); | ||||
|                 } | ||||
|             } else if (attribute.type === 'relation') { | ||||
|                 if (attribute.value) { | ||||
|                     $container.append('@' + attribute.name + "="); | ||||
|   | ||||
| @@ -41,7 +41,7 @@ class CalendarWidget extends StandardWidget { | ||||
|     } | ||||
|  | ||||
|     init($el, activeDate) { | ||||
|         this.activeDate = new Date(Date.parse(activeDate)); | ||||
|         this.activeDate = new Date(activeDate + "T12:00:00"); // attaching time fixes local timezone handling | ||||
|         this.todaysDate = new Date(); | ||||
|         this.date = new Date(this.activeDate.getTime()); | ||||
|  | ||||
|   | ||||
| @@ -43,6 +43,7 @@ body { | ||||
|     min-height: 0; | ||||
|     padding-left: 10px; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
| } | ||||
|  | ||||
| #search-box { | ||||
|   | ||||
							
								
								
									
										69
									
								
								src/public/stylesheets/detail.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/public/stylesheets/detail.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| .note-detail-book { | ||||
|     height: 100%; | ||||
| } | ||||
|  | ||||
| .note-detail-book-content { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     overflow: auto; | ||||
|     height: 100%; | ||||
|     align-content: start; | ||||
| } | ||||
|  | ||||
| .note-book-card { | ||||
|     border-radius: 10px; | ||||
|     background-color: var(--accented-background-color); | ||||
|     padding: 15px; | ||||
|     padding-bottom: 5px; | ||||
|     margin: 5px; | ||||
|     margin-left: 0; | ||||
|     overflow: hidden; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     flex-shrink: 0; | ||||
| } | ||||
|  | ||||
| .note-book-card .note-book-card { | ||||
|     border: 1px solid var(--main-border-color); | ||||
| } | ||||
|  | ||||
| .note-book-content { | ||||
|     overflow: hidden; | ||||
| } | ||||
|  | ||||
| .note-book-card.type-image .note-book-content, .note-book-card.type-file .note-book-content, .note-book-card.type-protected-session .note-book-content { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .note-book-card.type-image .note-book-content img, .note-book-card.type-text .note-book-content img { | ||||
|     max-width: 100%; | ||||
|     max-height: 100%; | ||||
| } | ||||
|  | ||||
| .note-book-title { | ||||
|     flex-grow: 0; | ||||
| } | ||||
|  | ||||
| .note-book-content { | ||||
|     flex-grow: 1; | ||||
| } | ||||
|  | ||||
| .note-book-auto-message { | ||||
|     background-color: var(--accented-background-color); | ||||
|     text-align: center; | ||||
|     width: 100%; | ||||
|     border-radius: 10px; | ||||
|     padding: 5px; | ||||
|     margin-top: 5px; | ||||
| } | ||||
|  | ||||
| .note-detail-image { | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .note-detail-image-view { | ||||
|     max-width: 100%; | ||||
| } | ||||
							
								
								
									
										11
									
								
								src/public/stylesheets/print.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/public/stylesheets/print.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| @media print | ||||
| { | ||||
|     .no-print, .no-print * | ||||
|     { | ||||
|         display: none !important; | ||||
|     } | ||||
|  | ||||
|     .relation-map-wrapper { | ||||
|         height: 100vh !important; | ||||
|     } | ||||
| } | ||||
| @@ -518,14 +518,6 @@ button.icon-button { | ||||
|     padding: 5px; | ||||
| } | ||||
|  | ||||
| .note-detail-image { | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .note-detail-image-view { | ||||
|     max-width: 100%; | ||||
| } | ||||
|  | ||||
| pre:not(.CodeMirror-line) { | ||||
|     color: var(--main-text-color) !important; | ||||
|     white-space: pre-wrap; | ||||
| @@ -857,68 +849,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href | ||||
|     z-index: 100; | ||||
| } | ||||
|  | ||||
| .note-detail-book { | ||||
|     height: 100%; | ||||
| } | ||||
|  | ||||
| .note-detail-book-content { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     overflow: auto; | ||||
|     height: 100%; | ||||
|     align-content: start; | ||||
| } | ||||
|  | ||||
| .note-book-card { | ||||
|     border-radius: 10px; | ||||
|     background-color: var(--accented-background-color); | ||||
|     padding: 15px; | ||||
|     padding-bottom: 5px; | ||||
|     margin: 5px; | ||||
|     margin-left: 0; | ||||
|     overflow: hidden; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     flex-shrink: 0; | ||||
| } | ||||
|  | ||||
| .note-book-card .note-book-card { | ||||
|     border: 1px solid var(--main-border-color); | ||||
| } | ||||
|  | ||||
| .note-book-content { | ||||
|     overflow: hidden; | ||||
| } | ||||
|  | ||||
| .note-book-card.type-image .note-book-content, .note-book-card.type-file .note-book-content, .note-book-card.type-protected-session .note-book-content { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .note-book-card.type-image .note-book-content img { | ||||
|     max-width: 100%; | ||||
|     max-height: 100%; | ||||
| } | ||||
|  | ||||
| .note-book-title { | ||||
|     flex-grow: 0; | ||||
| } | ||||
|  | ||||
| .note-book-content { | ||||
|     flex-grow: 1; | ||||
| } | ||||
|  | ||||
| .note-book-auto-message { | ||||
|     background-color: var(--accented-background-color); | ||||
|     text-align: center; | ||||
|     width: 100%; | ||||
|     border-radius: 10px; | ||||
|     padding: 5px; | ||||
|     margin-top: 5px; | ||||
| } | ||||
|  | ||||
| #toast-container { | ||||
|     position: absolute; | ||||
|     width: 100%; | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const app_info = require('../../services/app_info'); | ||||
| const appInfo = require('../../services/app_info'); | ||||
|  | ||||
| async function getAppInfo() { | ||||
|     return app_info; | ||||
|     return appInfo; | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const attributeService = require("../../services/attributes"); | ||||
| const noteService = require('../../services/notes'); | ||||
| const dateNoteService = require('../../services/date_notes'); | ||||
| const dateUtils = require('../../services/date_utils'); | ||||
| @@ -23,16 +24,26 @@ async function findClippingNote(todayNote, pageUrl) { | ||||
|     return null; | ||||
| } | ||||
|  | ||||
| async function getClipperInboxNote() { | ||||
|     let clipperInbox = await attributeService.getNoteWithLabel('clipperInbox'); | ||||
|  | ||||
|     if (!clipperInbox) { | ||||
|         clipperInbox = await dateNoteService.getDateNote(dateUtils.localNowDate()); | ||||
|     } | ||||
|  | ||||
|     return clipperInbox; | ||||
| } | ||||
|  | ||||
| async function addClipping(req) { | ||||
|     const {title, content, pageUrl, images} = req.body; | ||||
|  | ||||
|     const todayNote = await dateNoteService.getDateNote(dateUtils.localNowDate()); | ||||
|     const clipperInbox = await getClipperInboxNote(); | ||||
|  | ||||
|     let clippingNote = await findClippingNote(todayNote, pageUrl); | ||||
|     let clippingNote = await findClippingNote(clipperInbox, pageUrl); | ||||
|  | ||||
|     if (!clippingNote) { | ||||
|         clippingNote = (await noteService.createNewNote({ | ||||
|             parentNoteId: todayNote.noteId, | ||||
|             parentNoteId: clipperInbox.noteId, | ||||
|             title: title, | ||||
|             content: '', | ||||
|             type: 'text' | ||||
| @@ -54,10 +65,10 @@ async function addClipping(req) { | ||||
| async function createNote(req) { | ||||
|     const {title, content, pageUrl, images, clipType} = req.body; | ||||
|  | ||||
|     const todayNote = await dateNoteService.getDateNote(dateUtils.localNowDate()); | ||||
|     const clipperInbox = await getClipperInboxNote(); | ||||
|  | ||||
|     const {note} = await noteService.createNewNote({ | ||||
|         parentNoteId: todayNote.noteId, | ||||
|         parentNoteId: clipperInbox.noteId, | ||||
|         title, | ||||
|         content, | ||||
|         type: 'text' | ||||
|   | ||||
| @@ -49,7 +49,7 @@ async function loginSync(req) { | ||||
|  | ||||
|     return { | ||||
|         sourceId: sourceIdService.getCurrentSourceId(), | ||||
|         maxSyncId: await sql.getValue("SELECT MAX(id) FROM sync") | ||||
|         maxSyncId: await sql.getValue("SELECT MAX(id) FROM sync WHERE isSynced = 1") | ||||
|     }; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -50,7 +50,7 @@ async function getStats() { | ||||
| async function checkSync() { | ||||
|     return { | ||||
|         entityHashes: await contentHashService.getEntityHashes(), | ||||
|         maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync') | ||||
|         maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync WHERE isSynced = 1') | ||||
|     }; | ||||
| } | ||||
|  | ||||
| @@ -116,11 +116,11 @@ async function forceNoteSync(req) { | ||||
| async function getChanged(req) { | ||||
|     const lastSyncId = parseInt(req.query.lastSyncId); | ||||
|  | ||||
|     const syncs = await sql.getRows("SELECT * FROM sync WHERE id > ? LIMIT 1000", [lastSyncId]); | ||||
|     const syncs = await sql.getRows("SELECT * FROM sync WHERE isSynced = 1 AND id > ? LIMIT 1000", [lastSyncId]); | ||||
|  | ||||
|     return { | ||||
|         syncs: await syncService.getSyncRecords(syncs), | ||||
|         maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync') | ||||
|         maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync WHERE isSynced = 1') | ||||
|     }; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,7 @@ const build = require('./build'); | ||||
| const packageJson = require('../../package'); | ||||
| const {TRILIUM_DATA_DIR} = require('./data_dir'); | ||||
|  | ||||
| const APP_DB_VERSION = 157; | ||||
| const APP_DB_VERSION = 158; | ||||
| const SYNC_VERSION = 14; | ||||
| const CLIPPER_PROTOCOL_VERSION = "1.0"; | ||||
|  | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| module.exports = { buildDate:"2020-01-19T15:45:06+01:00", buildRevision: "ab535bf147edac113299f76f410ff88b2c06735b" }; | ||||
| module.exports = { buildDate:"2020-03-15T11:21:43+01:00", buildRevision: "f5e27278ab2a38484019ee2510781099b60ec2b6" }; | ||||
|   | ||||
| @@ -7,6 +7,7 @@ const noteService = require('./notes'); | ||||
| const repository = require('./repository'); | ||||
| const Branch = require('../entities/branch'); | ||||
| const TaskContext = require("./task_context.js"); | ||||
| const utils = require('./utils'); | ||||
|  | ||||
| async function cloneNoteToParent(noteId, parentNoteId, prefix) { | ||||
|     if (await isNoteDeleted(noteId) || await isNoteDeleted(parentNoteId)) { | ||||
| @@ -54,7 +55,8 @@ async function ensureNoteIsAbsentFromParent(noteId, parentNoteId) { | ||||
|     const branch = await repository.getEntity(`SELECT * FROM branches WHERE noteId = ? AND parentNoteId = ? AND isDeleted = 0`, [noteId, parentNoteId]); | ||||
|  | ||||
|     if (branch) { | ||||
|         await noteService.deleteBranch(branch, new TaskContext()); | ||||
|         const deleteId = utils.randomString(10); | ||||
|         await noteService.deleteBranch(branch, deleteId, new TaskContext()); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -51,7 +51,7 @@ class ConsistencyChecks { | ||||
|             childToParents[childNoteId].push(parentNoteId); | ||||
|         } | ||||
|  | ||||
|         function checkTreeCycle(noteId, path) { | ||||
|         const checkTreeCycle = (noteId, path) => { | ||||
|             if (noteId === 'root') { | ||||
|                 return; | ||||
|             } | ||||
| @@ -75,7 +75,7 @@ class ConsistencyChecks { | ||||
|                     checkTreeCycle(parentNoteId, newPath); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         }; | ||||
|  | ||||
|         const noteIds = Object.keys(childToParents); | ||||
|  | ||||
| @@ -323,14 +323,25 @@ class ConsistencyChecks { | ||||
|                     WHERE isErased = 1 | ||||
|                       AND content IS NOT NULL`, | ||||
|             async ({noteId}) => { | ||||
|             if (this.autoFix) { | ||||
|                 await sql.execute(`UPDATE note_contents SET content = NULL WHERE noteId = ?`, [noteId]); | ||||
|  | ||||
|                 logFix(`Note ${noteId} content has been set to null since the note is erased`); | ||||
|             } | ||||
|             else { | ||||
|                 logError(`Note ${noteId} content is not null even though the note is erased`); | ||||
|             } | ||||
|             // we always fix this issue because there does not seem to be a good way to prevent it. | ||||
|             // Scenario in which this can happen: | ||||
|             // 1. user on instance A deletes the note (sync for notes is created, but not for note_contents) and is later erased | ||||
|             // 2. instance B gets synced from instance A, note is updated because of sync row for notes, | ||||
|             //    but note_contents is not because erasion does not create sync rows | ||||
|             // 3. therefore note.isErased = true, but note_contents.content remains not updated and not erased. | ||||
|             // | ||||
|             // Considered solutions: | ||||
|             // - don't sync erased notes - this might prevent syncing also of the isDeleted flag and note would continue | ||||
|             //   to exist on the other instance | ||||
|             // - create sync rows for erased event - this would be a problem for undeletion since erasion might happen | ||||
|             //   on one instance after undelete and thus would win even though there's no user action behind it | ||||
|             // | ||||
|             // So instead we just fix such cases afterwards here. | ||||
|  | ||||
|             await sql.execute(`UPDATE note_contents SET content = NULL WHERE noteId = ?`, [noteId]); | ||||
|  | ||||
|             logFix(`Note ${noteId} content has been set to null since the note is erased`); | ||||
|         }); | ||||
|  | ||||
|         await this.findAndFixIssues(` | ||||
| @@ -535,7 +546,7 @@ class ConsistencyChecks { | ||||
|           ${entityName}  | ||||
|           LEFT JOIN sync ON sync.entityName = '${entityName}' AND entityId = ${key}  | ||||
|         WHERE  | ||||
|           sync.id IS NULL AND ` + (entityName === 'options' ? 'isSynced = 1' : '1'), | ||||
|           sync.id IS NULL AND ` + (entityName === 'options' ? 'options.isSynced = 1' : '1'), | ||||
|             async ({entityId}) => { | ||||
|                 if (this.autoFix) { | ||||
|                     await syncTableService.addEntitySync(entityName, entityId); | ||||
| @@ -547,23 +558,23 @@ class ConsistencyChecks { | ||||
|             }); | ||||
|  | ||||
|         await this.findAndFixIssues(` | ||||
|         SELECT  | ||||
|           id, entityId | ||||
|         FROM  | ||||
|           sync  | ||||
|           LEFT JOIN ${entityName} ON entityId = ${key}  | ||||
|         WHERE  | ||||
|           sync.entityName = '${entityName}'  | ||||
|           AND ${key} IS NULL`, | ||||
|             async ({id, entityId}) => { | ||||
|                 if (this.autoFix) { | ||||
|                     await sql.execute("DELETE FROM sync WHERE entityName = ? AND entityId = ?", [entityName, entityId]); | ||||
|             SELECT  | ||||
|               id, entityId | ||||
|             FROM  | ||||
|               sync  | ||||
|               LEFT JOIN ${entityName} ON entityId = ${key}  | ||||
|             WHERE  | ||||
|               sync.entityName = '${entityName}'  | ||||
|               AND ${key} IS NULL`, | ||||
|                 async ({id, entityId}) => { | ||||
|                     if (this.autoFix) { | ||||
|                         await sql.execute("DELETE FROM sync WHERE entityName = ? AND entityId = ?", [entityName, entityId]); | ||||
|  | ||||
|                     logFix(`Deleted extra sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); | ||||
|                 } else { | ||||
|                     logError(`Unrecognized sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); | ||||
|                 } | ||||
|             }); | ||||
|                         logFix(`Deleted extra sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); | ||||
|                     } else { | ||||
|                         logError(`Unrecognized sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); | ||||
|                     } | ||||
|                 }); | ||||
|     } | ||||
|  | ||||
|     async findSyncRowsIssues() { | ||||
|   | ||||
| @@ -235,8 +235,6 @@ async function importEnex(taskContext, file, parentNote) { | ||||
|  | ||||
|         taskContext.increaseProgressCount(); | ||||
|  | ||||
|         let noteContent = await noteEntity.getContent(); | ||||
|  | ||||
|         for (const resource of resources) { | ||||
|             const hash = utils.md5(resource.content); | ||||
|  | ||||
| @@ -268,7 +266,7 @@ async function importEnex(taskContext, file, parentNote) { | ||||
|  | ||||
|                 const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`; | ||||
|  | ||||
|                 noteContent = noteContent.replace(mediaRegex, resourceLink); | ||||
|                 content = content.replace(mediaRegex, resourceLink); | ||||
|             }; | ||||
|  | ||||
|             if (["image/jpeg", "image/png", "image/gif", "image/webp"].includes(resource.mime)) { | ||||
| @@ -281,12 +279,12 @@ async function importEnex(taskContext, file, parentNote) { | ||||
|  | ||||
|                     const imageLink = `<img src="${url}">`; | ||||
|  | ||||
|                     noteContent = noteContent.replace(mediaRegex, imageLink); | ||||
|                     content = content.replace(mediaRegex, imageLink); | ||||
|  | ||||
|                     if (!noteContent.includes(imageLink)) { | ||||
|                     if (!content.includes(imageLink)) { | ||||
|                         // if there wasn't any match for the reference, we'll add the image anyway | ||||
|                         // otherwise image would be removed since no note would include it | ||||
|                         noteContent += imageLink; | ||||
|                         content += imageLink; | ||||
|                     } | ||||
|                 } catch (e) { | ||||
|                     log.error("error when saving image from ENEX file: " + e); | ||||
| @@ -298,7 +296,7 @@ async function importEnex(taskContext, file, parentNote) { | ||||
|         } | ||||
|  | ||||
|         // save updated content with links to files/images | ||||
|         await noteEntity.setContent(noteContent); | ||||
|         await noteEntity.setContent(content); | ||||
|  | ||||
|         await noteService.scanForLinks(noteEntity.noteId); | ||||
|  | ||||
|   | ||||
| @@ -152,6 +152,11 @@ async function importTar(taskContext, fileBuffer, importRootNote) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (attr.type === 'label' && attr.name === 'externalLink') { | ||||
|                 // also created automatically | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (attr.type === 'relation') { | ||||
|                 attr.value = getNewNoteId(attr.value); | ||||
|             } | ||||
|   | ||||
| @@ -242,6 +242,20 @@ function findInternalLinks(content, foundLinks) { | ||||
|     return content.replace(/href="[^"]*#root/g, 'href="#root'); | ||||
| } | ||||
|  | ||||
| function findExternalLinks(content, foundLinks) { | ||||
|     const re = /href="([a-zA-Z]+:\/\/[^"]*)"/g; | ||||
|     let match; | ||||
|  | ||||
|     while (match = re.exec(content)) { | ||||
|         foundLinks.push({ | ||||
|             name: 'externalLink', | ||||
|             value: match[1] | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     return content; | ||||
| } | ||||
|  | ||||
| function findIncludeNoteLinks(content, foundLinks) { | ||||
|     const re = /<section class="include-note" data-note-id="([a-zA-Z0-9]+)">/g; | ||||
|     let match; | ||||
| @@ -281,6 +295,7 @@ async function saveLinks(note, content) { | ||||
|     if (note.type === 'text') { | ||||
|         content = findImageLinks(content, foundLinks); | ||||
|         content = findInternalLinks(content, foundLinks); | ||||
|         content = findExternalLinks(content, foundLinks); | ||||
|         content = findIncludeNoteLinks(content, foundLinks); | ||||
|     } | ||||
|     else if (note.type === 'relation-map') { | ||||
| @@ -293,9 +308,11 @@ async function saveLinks(note, content) { | ||||
|     const existingLinks = await note.getLinks(); | ||||
|  | ||||
|     for (const foundLink of foundLinks) { | ||||
|         const targetNote = await repository.getNote(foundLink.value); | ||||
|         if (!targetNote || targetNote.isDeleted) { | ||||
|             continue; | ||||
|         if (foundLink.name !== 'externalLink') { | ||||
|             const targetNote = await repository.getNote(foundLink.value); | ||||
|             if (!targetNote || targetNote.isDeleted) { | ||||
|                 continue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         const existingLink = existingLinks.find(existingLink => | ||||
| @@ -305,7 +322,7 @@ async function saveLinks(note, content) { | ||||
|         if (!existingLink) { | ||||
|             await new Attribute({ | ||||
|                 noteId: note.noteId, | ||||
|                 type: 'relation', | ||||
|                 type: foundLink.name === 'externalLink' ? 'label' : 'relation', | ||||
|                 name: foundLink.name, | ||||
|                 value: foundLink.value, | ||||
|             }).save(); | ||||
| @@ -582,6 +599,7 @@ async function eraseDeletedNotes() { | ||||
|         UPDATE notes  | ||||
|         SET title = '[deleted]', | ||||
|             contentLength = 0, | ||||
|             isProtected = 0, | ||||
|             isErased = 1 | ||||
|         WHERE noteId IN (???)`, noteIdsToErase); | ||||
|  | ||||
|   | ||||
| @@ -209,6 +209,8 @@ async function transactional(func) { | ||||
|  | ||||
|             transactionActive = false; | ||||
|             resolve(); | ||||
|  | ||||
|             setTimeout(() => require('./ws').sendPingToAllClients(), 50); | ||||
|         } | ||||
|         catch (e) { | ||||
|             if (transactionActive) { | ||||
|   | ||||
| @@ -176,7 +176,7 @@ async function pushSync(syncContext) { | ||||
|     let lastSyncedPush = await getLastSyncedPush(); | ||||
|  | ||||
|     while (true) { | ||||
|         const syncs = await sql.getRows('SELECT * FROM sync WHERE id > ? LIMIT 1000', [lastSyncedPush]); | ||||
|         const syncs = await sql.getRows('SELECT * FROM sync WHERE isSynced = 1 AND id > ? LIMIT 1000', [lastSyncedPush]); | ||||
|  | ||||
|         if (syncs.length === 0) { | ||||
|             log.info("Nothing to push"); | ||||
| @@ -228,14 +228,15 @@ async function syncFinished(syncContext) { | ||||
|  | ||||
| async function checkContentHash(syncContext) { | ||||
|     const resp = await syncRequest(syncContext, 'GET', '/api/sync/check'); | ||||
|     const lastSyncedPullId = await getLastSyncedPull(); | ||||
|  | ||||
|     if (await getLastSyncedPull() < resp.maxSyncId) { | ||||
|         log.info("There are some outstanding pulls, skipping content check."); | ||||
|     if (lastSyncedPullId < resp.maxSyncId) { | ||||
|         log.info(`There are some outstanding pulls (${lastSyncedPullId} vs. ${resp.maxSyncId}), skipping content check.`); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     const notPushedSyncs = await sql.getValue("SELECT EXISTS(SELECT 1 FROM sync WHERE id > ?)", [await getLastSyncedPush()]); | ||||
|     const notPushedSyncs = await sql.getValue("SELECT EXISTS(SELECT 1 FROM sync WHERE isSynced = 1 AND id > ?)", [await getLastSyncedPush()]); | ||||
|  | ||||
|     if (notPushedSyncs) { | ||||
|         log.info(`There's ${notPushedSyncs} outstanding pushes, skipping content check.`); | ||||
| @@ -352,7 +353,7 @@ async function updatePushStats() { | ||||
|     if (await syncOptions.isSyncSetup()) { | ||||
|         const lastSyncedPush = await optionService.getOption('lastSyncedPush'); | ||||
|  | ||||
|         stats.outstandingPushes = await sql.getValue("SELECT COUNT(1) FROM sync WHERE id > ?", [lastSyncedPush]); | ||||
|         stats.outstandingPushes = await sql.getValue("SELECT COUNT(1) FROM sync WHERE isSynced = 1 AND id > ?", [lastSyncedPush]); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,8 @@ async function insertEntitySync(entityName, entityId, sourceId) { | ||||
|         entityName: entityName, | ||||
|         entityId: entityId, | ||||
|         utcSyncDate: dateUtils.utcNowDateTime(), | ||||
|         sourceId: sourceId || cls.getSourceId() || sourceIdService.getCurrentSourceId() | ||||
|         sourceId: sourceId || cls.getSourceId() || sourceIdService.getCurrentSourceId(), | ||||
|         isSynced: 1 | ||||
|     }; | ||||
|  | ||||
|     sync.id = await sql.replace("sync", sync); | ||||
| @@ -23,8 +24,6 @@ async function addEntitySync(entityName, entityId, sourceId) { | ||||
|     const sync = await insertEntitySync(entityName, entityId, sourceId); | ||||
|  | ||||
|     syncs.push(sync); | ||||
|  | ||||
|     setTimeout(() => require('./ws').sendPingToAllClients(), 50); | ||||
| } | ||||
|  | ||||
| async function addEntitySyncsForSector(entityName, entityPrimaryKey, sector) { | ||||
|   | ||||
| @@ -250,6 +250,7 @@ | ||||
|  | ||||
| <link href="stylesheets/themes.css" rel="stylesheet"> | ||||
| <link href="stylesheets/style.css" rel="stylesheet"> | ||||
| <link href="stylesheets/detail.css" rel="stylesheet"> | ||||
| <link href="stylesheets/desktop.css" rel="stylesheet"> | ||||
|  | ||||
| <script src="javascripts/desktop.js" crossorigin type="module"></script> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <div class="note-detail-image note-detail-component"> | ||||
|     <div style="display: flex; justify-content: space-evenly; margin: 10px;"> | ||||
|     <div class="no-print" style="display: flex; justify-content: space-evenly; margin: 10px;"> | ||||
|         <button class="image-download btn btn-sm btn-primary" type="button">Download</button> | ||||
|  | ||||
|         <button class="image-copy-to-clipboard btn btn-sm btn-primary" type="button">Copy to clipboard</button> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <div class="note-detail-relation-map note-detail-component"> | ||||
|     <button class="relation-map-create-child-note btn btn-sm floating-button" type="button" | ||||
|     <button class="relation-map-create-child-note btn btn-sm floating-button no-print" type="button" | ||||
|             title="Create new child note and add it into this relation map"> | ||||
|         <span class="bx bx-folder-plus"></span> | ||||
|  | ||||
| @@ -7,11 +7,11 @@ | ||||
|     </button> | ||||
|  | ||||
|     <button type="button" | ||||
|             class="relation-map-reset-pan-zoom btn icon-button floating-button bx bx-crop" | ||||
|             class="relation-map-reset-pan-zoom btn icon-button floating-button bx bx-crop no-print" | ||||
|             title="Reset pan & zoom to initial coordinates and magnification" | ||||
|             style="right: 70px;"></button> | ||||
|  | ||||
|     <div class="btn-group floating-button" style="right: 10px;"> | ||||
|     <div class="btn-group floating-button no-print" style="right: 10px;"> | ||||
|         <button type="button" | ||||
|                 class="relation-map-zoom-in btn icon-button bx bx-zoom-in" | ||||
|                 title="Zoom In"></button> | ||||
|   | ||||
| @@ -73,6 +73,8 @@ | ||||
|                         <% include details/relation_map.ejs %> | ||||
|  | ||||
|                         <% include details/protected_session_password.ejs %> | ||||
|  | ||||
|                         <% include details/book.ejs %> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
| @@ -116,6 +118,7 @@ | ||||
|  | ||||
| <link href="stylesheets/themes.css" rel="stylesheet"> | ||||
| <link href="stylesheets/style.css" rel="stylesheet"> | ||||
| <link href="stylesheets/detail.css" rel="stylesheet"> | ||||
| <link href="stylesheets/mobile.css" rel="stylesheet"> | ||||
|  | ||||
| <link rel="stylesheet" type="text/css" href="libraries/boxicons/css/boxicons.min.css"> | ||||
|   | ||||
| @@ -19,13 +19,13 @@ | ||||
|                 <label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupType"> | ||||
|                     I'm a new user and I want to create new Trilium document for my notes</label> | ||||
|             </div> | ||||
|             <div class="radio" data-bind="if: instanceType == 'server'" style="margin-bottom: 15px;"> | ||||
|             <div class="radio" style="margin-bottom: 15px;"> | ||||
|                 <label><input type="radio" name="setup-type" value="sync-from-desktop" data-bind="checked: setupType"> | ||||
|                     I have desktop instance already and I want to setup sync with it</label> | ||||
|             </div> | ||||
|             <div class="radio" data-bind="if: instanceType == 'desktop'" style="margin-bottom: 15px;"> | ||||
|             <div class="radio" style="margin-bottom: 15px;"> | ||||
|                 <label><input type="radio" name="setup-type" value="sync-from-server" data-bind="checked: setupType"> | ||||
|                     I have server instance up and I want to setup sync with it</label> | ||||
|                     I have server instance already and I want to setup sync with it</label> | ||||
|             </div> | ||||
|  | ||||
|             <button type="button" data-bind="disable: !setupTypeSelected(), click: selectSetupType" class="btn btn-primary">Next</button> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user