mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Compare commits
	
		
			18 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e8a9e49e9e | ||
|  | fb55cdaea6 | ||
|  | b9b2cc8364 | ||
|  | 8dfdd090f5 | ||
|  | fe7705524a | ||
|  | 2b1b7774f8 | ||
|  | 2d58019d6e | ||
|  | fe31f08c0d | ||
|  | ad7a55d305 | ||
|  | 4ce4ac9584 | ||
|  | 88bd65c679 | ||
|  | 9eab3026bb | ||
|  | 7abaedbf31 | ||
|  | 402718d293 | ||
|  | 990a84c202 | ||
|  | d8e181a828 | ||
|  | adb8caa8a2 | ||
|  | 9a13edd490 | 
							
								
								
									
										3
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							| @@ -6,9 +6,6 @@ | |||||||
|       <synchronize>true</synchronize> |       <synchronize>true</synchronize> | ||||||
|       <jdbc-driver>org.sqlite.JDBC</jdbc-driver> |       <jdbc-driver>org.sqlite.JDBC</jdbc-driver> | ||||||
|       <jdbc-url>jdbc:sqlite:$PROJECT_DIR$/../trilium-data/document.db</jdbc-url> |       <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> |     </data-source> | ||||||
|   </component> |   </component> | ||||||
| </project> | </project> | ||||||
| @@ -26,9 +26,9 @@ app.on('ready', async () => { | |||||||
|  |  | ||||||
|     await sqlInit.dbConnection; |     await sqlInit.dbConnection; | ||||||
|  |  | ||||||
|     // if schema doesn't exist -> setup process |     // if db is not initialized -> setup process | ||||||
|     // if schema exists, then we need to wait until the migration process is finished |     // if db is initialized, then we need to wait until the migration process is finished | ||||||
|     if (await sqlInit.schemaExists()) { |     if (await sqlInit.isDbInitialized()) { | ||||||
|         await sqlInit.dbReady; |         await sqlInit.dbReady; | ||||||
|  |  | ||||||
|         await windowService.createMainWindow(); |         await windowService.createMainWindow(); | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "version": "0.40.1", |   "version": "0.40.3", | ||||||
|   "lockfileVersion": 1, |   "lockfileVersion": 1, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "productName": "Trilium Notes", |   "productName": "Trilium Notes", | ||||||
|   "description": "Trilium Notes", |   "description": "Trilium Notes", | ||||||
|   "version": "0.40.2", |   "version": "0.40.4", | ||||||
|   "license": "AGPL-3.0-only", |   "license": "AGPL-3.0-only", | ||||||
|   "main": "electron.js", |   "main": "electron.js", | ||||||
|   "bin": { |   "bin": { | ||||||
|   | |||||||
| @@ -811,8 +811,10 @@ class Note extends Entity { | |||||||
|             FROM attributes  |             FROM attributes  | ||||||
|             WHERE noteId = ? AND  |             WHERE noteId = ? AND  | ||||||
|                   isDeleted = 0 AND  |                   isDeleted = 0 AND  | ||||||
|                   type = 'relation' AND  |                   ((type = 'relation' AND  | ||||||
|                   name IN ('internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink')`, [this.noteId]); |                     name IN ('internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink')) | ||||||
|  |                   OR | ||||||
|  |                    (type = 'label' AND name = 'externalLink'))`, [this.noteId]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -10,8 +10,6 @@ const $buildRevision = $("#build-revision"); | |||||||
| const $dataDirectory = $("#data-directory"); | const $dataDirectory = $("#data-directory"); | ||||||
|  |  | ||||||
| export async function showDialog() { | export async function showDialog() { | ||||||
|     utils.closeActiveDialog(); |  | ||||||
|  |  | ||||||
|     const appInfo = await server.get('app-info'); |     const appInfo = await server.get('app-info'); | ||||||
|  |  | ||||||
|     $appVersion.text(appInfo.appVersion); |     $appVersion.text(appInfo.appVersion); | ||||||
| @@ -22,7 +20,5 @@ export async function showDialog() { | |||||||
|     $buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision); |     $buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision); | ||||||
|     $dataDirectory.text(appInfo.dataDirectory); |     $dataDirectory.text(appInfo.dataDirectory); | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     $dialog.modal(); |  | ||||||
| } | } | ||||||
| @@ -11,13 +11,9 @@ const $linkTitle = $("#link-title"); | |||||||
| const $addLinkTitleFormGroup = $("#add-link-title-form-group"); | const $addLinkTitleFormGroup = $("#add-link-title-form-group"); | ||||||
|  |  | ||||||
| export async function showDialog() { | export async function showDialog() { | ||||||
|     utils.closeActiveDialog(); |  | ||||||
|  |  | ||||||
|     $addLinkTitleFormGroup.toggle(!hasSelection()); |     $addLinkTitleFormGroup.toggle(!hasSelection()); | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     $dialog.modal(); |  | ||||||
|  |  | ||||||
|     $autoComplete.val('').trigger('focus'); |     $autoComplete.val('').trigger('focus'); | ||||||
|     $linkTitle.val(''); |     $linkTitle.val(''); | ||||||
|   | |||||||
| @@ -287,8 +287,6 @@ function initKoPlugins() { | |||||||
| } | } | ||||||
|  |  | ||||||
| export async function showDialog() { | export async function showDialog() { | ||||||
|     utils.closeActiveDialog(); |  | ||||||
|  |  | ||||||
|     await libraryLoader.requireLibrary(libraryLoader.KNOCKOUT); |     await libraryLoader.requireLibrary(libraryLoader.KNOCKOUT); | ||||||
|  |  | ||||||
|     // lazily apply bindings on first use |     // lazily apply bindings on first use | ||||||
| @@ -300,11 +298,9 @@ export async function showDialog() { | |||||||
|         ko.applyBindings(attributesModel, $dialog[0]); |         ko.applyBindings(attributesModel, $dialog[0]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     await attributesModel.loadAttributes(); |     await attributesModel.loadAttributes(); | ||||||
|  |  | ||||||
|     $dialog.modal(); |     utils.openDialog($dialog); | ||||||
| } | } | ||||||
|  |  | ||||||
| $dialog.on('focus', '.attribute-name', function (e) { | $dialog.on('focus', '.attribute-name', function (e) { | ||||||
|   | |||||||
| @@ -6,11 +6,7 @@ const $backendLogTextArea = $("#backend-log-textarea"); | |||||||
| const $refreshBackendLog = $("#refresh-backend-log-button"); | const $refreshBackendLog = $("#refresh-backend-log-button"); | ||||||
|  |  | ||||||
| export async function showDialog() { | export async function showDialog() { | ||||||
|     utils.closeActiveDialog(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     $dialog.modal(); |  | ||||||
|  |  | ||||||
|     load(); |     load(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,10 +13,6 @@ const $noteTitle = $('#branch-prefix-note-title'); | |||||||
| let branchId; | let branchId; | ||||||
|  |  | ||||||
| export async function showDialog(node) { | export async function showDialog(node) { | ||||||
|     utils.closeActiveDialog(); |  | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     branchId = node.data.branchId; |     branchId = node.data.branchId; | ||||||
|     const branch = treeCache.getBranch(branchId); |     const branch = treeCache.getBranch(branchId); | ||||||
|  |  | ||||||
| @@ -30,7 +26,7 @@ export async function showDialog(node) { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     $dialog.modal(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     $treePrefixInput.val(branch.prefix); |     $treePrefixInput.val(branch.prefix); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,11 +22,7 @@ export async function showDialog(noteIds) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     utils.closeActiveDialog(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     $dialog.modal(); |  | ||||||
|  |  | ||||||
|     $noteAutoComplete.val('').trigger('focus'); |     $noteAutoComplete.val('').trigger('focus'); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,8 +17,6 @@ let taskId = ''; | |||||||
| let branchId = null; | let branchId = null; | ||||||
|  |  | ||||||
| export async function showDialog(node, defaultType) { | 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 |     // each opening of the dialog resets the taskId so we don't associate it with previous exports anymore | ||||||
|     taskId = ''; |     taskId = ''; | ||||||
|     $exportButton.removeAttr("disabled"); |     $exportButton.removeAttr("disabled"); | ||||||
| @@ -38,9 +36,7 @@ export async function showDialog(node, defaultType) { | |||||||
|  |  | ||||||
|     $("#opml-v2").prop("checked", true); // setting default |     $("#opml-v2").prop("checked", true); // setting default | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     $dialog.modal(); |  | ||||||
|  |  | ||||||
|     branchId = node.data.branchId; |     branchId = node.data.branchId; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,9 +3,5 @@ import utils from "../services/utils.js"; | |||||||
| const $dialog = $("#help-dialog"); | const $dialog = $("#help-dialog"); | ||||||
|  |  | ||||||
| export async function showDialog() { | export async function showDialog() { | ||||||
|     utils.closeActiveDialog(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     $dialog.modal(); |  | ||||||
| } | } | ||||||
| @@ -16,8 +16,6 @@ const $explodeArchivesCheckbox = $("#explode-archives-checkbox"); | |||||||
| let parentNoteId = null; | let parentNoteId = null; | ||||||
|  |  | ||||||
| export async function showDialog(node) { | export async function showDialog(node) { | ||||||
|     utils.closeActiveDialog(); |  | ||||||
|  |  | ||||||
|     $fileUploadInput.val('').trigger('change'); // to trigger Import button disabling listener below |     $fileUploadInput.val('').trigger('change'); // to trigger Import button disabling listener below | ||||||
|  |  | ||||||
|     $safeImportCheckbox.prop("checked", true); |     $safeImportCheckbox.prop("checked", true); | ||||||
| @@ -26,13 +24,11 @@ export async function showDialog(node) { | |||||||
|     $codeImportedAsCodeCheckbox.prop("checked", true); |     $codeImportedAsCodeCheckbox.prop("checked", true); | ||||||
|     $explodeArchivesCheckbox.prop("checked", true); |     $explodeArchivesCheckbox.prop("checked", true); | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     parentNoteId = node.data.noteId; |     parentNoteId = node.data.noteId; | ||||||
|  |  | ||||||
|     $noteTitle.text(await treeUtils.getNoteTitle(parentNoteId)); |     $noteTitle.text(await treeUtils.getNoteTitle(parentNoteId)); | ||||||
|  |  | ||||||
|     $dialog.modal(); |     utils.openDialog($dialog); | ||||||
| } | } | ||||||
|  |  | ||||||
| $form.on('submit', () => { | $form.on('submit', () => { | ||||||
|   | |||||||
| @@ -10,13 +10,9 @@ let callback = null; | |||||||
| export async function showDialog(cb) { | export async function showDialog(cb) { | ||||||
|     callback = cb; |     callback = cb; | ||||||
|  |  | ||||||
|     utils.closeActiveDialog(); |  | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     $autoComplete.val(''); |     $autoComplete.val(''); | ||||||
|  |  | ||||||
|     $dialog.modal(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     noteAutocompleteService.initNoteAutocomplete($autoComplete, { hideGoToSelectedNoteButton: true }); |     noteAutocompleteService.initNoteAutocomplete($autoComplete, { hideGoToSelectedNoteButton: true }); | ||||||
|     noteAutocompleteService.showRecentNotes($autoComplete); |     noteAutocompleteService.showRecentNotes($autoComplete); | ||||||
|   | |||||||
| @@ -10,13 +10,9 @@ let $originallyFocused; // element focused before the dialog was opened so we ca | |||||||
| export function info(message) { | export function info(message) { | ||||||
|     $originallyFocused = $(':focus'); |     $originallyFocused = $(':focus'); | ||||||
|  |  | ||||||
|     utils.closeActiveDialog(); |  | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     $infoContent.text(message); |     $infoContent.text(message); | ||||||
|  |  | ||||||
|     $dialog.modal(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     return new Promise((res, rej) => { resolve = res; }); |     return new Promise((res, rej) => { resolve = res; }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,13 +8,9 @@ const $autoComplete = $("#jump-to-note-autocomplete"); | |||||||
| const $showInFullTextButton = $("#show-in-full-text-button"); | const $showInFullTextButton = $("#show-in-full-text-button"); | ||||||
|  |  | ||||||
| export async function showDialog() { | export async function showDialog() { | ||||||
|     utils.closeActiveDialog(); |  | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     $autoComplete.val(''); |     $autoComplete.val(''); | ||||||
|  |  | ||||||
|     $dialog.modal(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     noteAutocompleteService.initNoteAutocomplete($autoComplete, { hideGoToSelectedNoteButton: true }) |     noteAutocompleteService.initNoteAutocomplete($autoComplete, { hideGoToSelectedNoteButton: true }) | ||||||
|         .on('autocomplete:selected', function(event, suggestion, dataset) { |         .on('autocomplete:selected', function(event, suggestion, dataset) { | ||||||
|   | |||||||
| @@ -16,10 +16,6 @@ function getOptions() { | |||||||
| } | } | ||||||
|  |  | ||||||
| export async function showDialog() { | export async function showDialog() { | ||||||
|     utils.closeActiveDialog(); |  | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     // set default settings |     // set default settings | ||||||
|     $maxNotesInput.val(20); |     $maxNotesInput.val(20); | ||||||
|  |  | ||||||
| @@ -27,7 +23,7 @@ export async function showDialog() { | |||||||
|  |  | ||||||
|     $linkMapContainer.empty(); |     $linkMapContainer.empty(); | ||||||
|  |  | ||||||
|     $dialog.modal(); |     utils.openDialog($dialog); | ||||||
| } | } | ||||||
|  |  | ||||||
| $dialog.on('shown.bs.modal', () => { | $dialog.on('shown.bs.modal', () => { | ||||||
|   | |||||||
| @@ -37,9 +37,7 @@ export async function importMarkdownInline() { | |||||||
|         convertMarkdownToHtml(text); |         convertMarkdownToHtml(text); | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|         glob.activeDialog = $dialog; |         utils.openDialog($dialog); | ||||||
|  |  | ||||||
|         $dialog.modal(); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| import noteAutocompleteService from "../services/note_autocomplete.js"; | import noteAutocompleteService from "../services/note_autocomplete.js"; | ||||||
| import utils from "../services/utils.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 toastService from "../services/toast.js"; | ||||||
| import treeCache from "../services/tree_cache.js"; | import treeCache from "../services/tree_cache.js"; | ||||||
| import treeChangesService from "../services/branches.js"; | import treeChangesService from "../services/branches.js"; | ||||||
| @@ -18,11 +16,7 @@ let movedNodes; | |||||||
| export async function showDialog(nodes) { | export async function showDialog(nodes) { | ||||||
|     movedNodes = nodes; |     movedNodes = nodes; | ||||||
|  |  | ||||||
|     utils.closeActiveDialog(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     $dialog.modal(); |  | ||||||
|  |  | ||||||
|     $noteAutoComplete.val('').trigger('focus'); |     $noteAutoComplete.val('').trigger('focus'); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,11 +10,7 @@ const $mime = $("#note-info-mime"); | |||||||
| const $okButton = $("#note-info-ok-button"); | const $okButton = $("#note-info-ok-button"); | ||||||
|  |  | ||||||
| export function showDialog() { | export function showDialog() { | ||||||
|     utils.closeActiveDialog(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     $dialog.modal(); |  | ||||||
|  |  | ||||||
|     const activeNote = noteDetailService.getActiveTabNote(); |     const activeNote = noteDetailService.getActiveTabNote(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -29,11 +29,7 @@ export async function showCurrentNoteRevisions() { | |||||||
| } | } | ||||||
|  |  | ||||||
| export async function showNoteRevisionsDialog(noteId, noteRevisionId) { | export async function showNoteRevisionsDialog(noteId, noteRevisionId) { | ||||||
|     utils.closeActiveDialog(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     $dialog.modal(); |  | ||||||
|  |  | ||||||
|     await loadNoteRevisions(noteId, noteRevisionId); |     await loadNoteRevisions(noteId, noteRevisionId); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,11 +5,7 @@ const $dialog = $("#note-source-dialog"); | |||||||
| const $noteSource = $("#note-source"); | const $noteSource = $("#note-source"); | ||||||
|  |  | ||||||
| export function showDialog() { | export function showDialog() { | ||||||
|     utils.closeActiveDialog(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     $dialog.modal(); |  | ||||||
|  |  | ||||||
|     const noteText = noteDetailService.getActiveTabNote().content; |     const noteText = noteDetailService.getActiveTabNote().content; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,13 +6,9 @@ import utils from "../services/utils.js"; | |||||||
| const $dialog = $("#options-dialog"); | const $dialog = $("#options-dialog"); | ||||||
|  |  | ||||||
| export async function showDialog() { | export async function showDialog() { | ||||||
|     utils.closeActiveDialog(); |  | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     const options = await server.get('options'); |     const options = await server.get('options'); | ||||||
|  |  | ||||||
|     $dialog.modal(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     (await Promise.all([ |     (await Promise.all([ | ||||||
|         import('./options/advanced.js'), |         import('./options/advanced.js'), | ||||||
|   | |||||||
| @@ -12,10 +12,6 @@ let resolve; | |||||||
| let shownCb; | let shownCb; | ||||||
|  |  | ||||||
| export function ask({ message, defaultValue, shown }) { | export function ask({ message, defaultValue, shown }) { | ||||||
|     utils.closeActiveDialog(); |  | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     shownCb = shown; |     shownCb = shown; | ||||||
|  |  | ||||||
|     $question = $("<label>") |     $question = $("<label>") | ||||||
| @@ -34,7 +30,7 @@ export function ask({ message, defaultValue, shown }) { | |||||||
|             .append($question) |             .append($question) | ||||||
|             .append($answer)); |             .append($answer)); | ||||||
|  |  | ||||||
|     $dialog.modal(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     return new Promise((res, rej) => { resolve = res; }); |     return new Promise((res, rej) => { resolve = res; }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,11 +1,12 @@ | |||||||
| import protectedSessionService from "../services/protected_session.js"; | import protectedSessionService from "../services/protected_session.js"; | ||||||
|  | import utils from "../services/utils.js"; | ||||||
|  |  | ||||||
| const $dialog = $("#protected-session-password-dialog"); | const $dialog = $("#protected-session-password-dialog"); | ||||||
| const $passwordForm = $dialog.find(".protected-session-password-form"); | const $passwordForm = $dialog.find(".protected-session-password-form"); | ||||||
| const $passwordInput = $dialog.find(".protected-session-password"); | const $passwordInput = $dialog.find(".protected-session-password"); | ||||||
|  |  | ||||||
| export function show() { | export function show() { | ||||||
|     $dialog.modal(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     $passwordInput.trigger('focus'); |     $passwordInput.trigger('focus'); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,11 +8,7 @@ const $dialog = $("#recent-changes-dialog"); | |||||||
| const $content = $("#recent-changes-content"); | const $content = $("#recent-changes-content"); | ||||||
|  |  | ||||||
| export async function showDialog() { | export async function showDialog() { | ||||||
|     utils.closeActiveDialog(); |     utils.openDialog($dialog); | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     $dialog.modal(); |  | ||||||
|  |  | ||||||
|     const result = await server.get('recent-changes'); |     const result = await server.get('recent-changes'); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,13 +14,9 @@ let codeEditor; | |||||||
| $dialog.on("shown.bs.modal", e => initEditor()); | $dialog.on("shown.bs.modal", e => initEditor()); | ||||||
|  |  | ||||||
| export async function showDialog() { | export async function showDialog() { | ||||||
|     utils.closeActiveDialog(); |  | ||||||
|  |  | ||||||
|     glob.activeDialog = $dialog; |  | ||||||
|  |  | ||||||
|     await showTableSchemas(); |     await showTableSchemas(); | ||||||
|  |  | ||||||
|     $dialog.modal(); |     utils.openDialog($dialog); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function initEditor() { | async function initEditor() { | ||||||
|   | |||||||
| @@ -7,9 +7,26 @@ import contextMenuWidget from "./services/context_menu.js"; | |||||||
| import treeChangesService from "./services/branches.js"; | import treeChangesService from "./services/branches.js"; | ||||||
| import utils from "./services/utils.js"; | import utils from "./services/utils.js"; | ||||||
| import treeUtils from "./services/tree_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.isDesktop = utils.isDesktop; | ||||||
| window.glob.isMobile = utils.isMobile; | 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 $leftPane = $("#left-pane"); | ||||||
| const $tree = $("#tree"); | const $tree = $("#tree"); | ||||||
|   | |||||||
| @@ -7,31 +7,20 @@ import protectedSessionHolder from "./protected_session_holder.js"; | |||||||
| async function getRenderedContent(note) { | async function getRenderedContent(note) { | ||||||
|     const type = getRenderingType(note); |     const type = getRenderingType(note); | ||||||
|  |  | ||||||
|     let rendered; |     let $rendered; | ||||||
|  |  | ||||||
|     if (type === 'text') { |     if (type === 'text') { | ||||||
|         const fullNote = await server.get('notes/' + note.noteId); |         const fullNote = await server.get('notes/' + note.noteId); | ||||||
|  |  | ||||||
|         const $content = $("<div>").html(fullNote.content); |         $rendered = $("<div>").html(fullNote.content); | ||||||
|  |  | ||||||
|         if (utils.isHtmlEmpty(fullNote.content)) { |  | ||||||
|             rendered = ""; |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             rendered = $content; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|     else if (type === 'code') { |     else if (type === 'code') { | ||||||
|         const fullNote = await server.get('notes/' + note.noteId); |         const fullNote = await server.get('notes/' + note.noteId); | ||||||
|  |  | ||||||
|         if (fullNote.content.trim() === "") { |         $rendered = $("<pre>").text(fullNote.content); | ||||||
|             rendered = ""; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         rendered = $("<pre>").text(fullNote.content); |  | ||||||
|     } |     } | ||||||
|     else if (type === 'image') { |     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') { |     else if (type === 'file') { | ||||||
|         function getFileUrl() { |         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 |         // open doesn't work for protected notes since it works through browser which isn't in protected session | ||||||
|         $openButton.toggle(!note.isProtected); |         $openButton.toggle(!note.isProtected); | ||||||
|  |  | ||||||
|         rendered = $('<div>') |         $rendered = $('<div>') | ||||||
|             .append($downloadButton) |             .append($downloadButton) | ||||||
|             .append('   ') |             .append('   ') | ||||||
|             .append($openButton); |             .append($openButton); | ||||||
|     } |     } | ||||||
|     else if (type === 'render') { |     else if (type === 'render') { | ||||||
|         const $el = $('<div>'); |         $rendered = $('<div>'); | ||||||
|  |  | ||||||
|         await renderService.render(note, $el, this.ctx); |         await renderService.render(note, $rendered, this.ctx); | ||||||
|  |  | ||||||
|         rendered = $el; |  | ||||||
|     } |     } | ||||||
|     else if (type === 'protected-session') { |     else if (type === 'protected-session') { | ||||||
|         const $button = $(`<button class="btn btn-sm"><span class="bx bx-log-in"></span> Enter protected session</button>`) |         const $button = $(`<button class="btn btn-sm"><span class="bx bx-log-in"></span> Enter protected session</button>`) | ||||||
|             .on('click', protectedSessionService.enterProtectedSession); |             .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("<div>This note is protected and to access it you need to enter password.</div>") | ||||||
|             .append("<br/>") |             .append("<br/>") | ||||||
|             .append($button); |             .append($button); | ||||||
|     } |     } | ||||||
|     else { |     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 { |     return { | ||||||
|         renderedContent: rendered, |         renderedContent: $rendered, | ||||||
|         type |         type | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import treeService from './tree.js'; | |||||||
| import treeCache from "./tree_cache.js"; | import treeCache from "./tree_cache.js"; | ||||||
| import server from './server.js'; | import server from './server.js'; | ||||||
| import toastService from "./toast.js"; | import toastService from "./toast.js"; | ||||||
|  | import utils from "./utils.js"; | ||||||
|  |  | ||||||
| const $searchInput = $("input[name='search-text']"); | const $searchInput = $("input[name='search-text']"); | ||||||
| const $resetSearchButton = $("#reset-search-button"); | const $resetSearchButton = $("#reset-search-button"); | ||||||
| @@ -28,6 +29,8 @@ const helpText = ` | |||||||
| </p>`; | </p>`; | ||||||
|  |  | ||||||
| function showSearch() { | function showSearch() { | ||||||
|  |     utils.saveFocusedElement(); | ||||||
|  |  | ||||||
|     $searchBox.slideDown(); |     $searchBox.slideDown(); | ||||||
|  |  | ||||||
|     $searchBox.tooltip({ |     $searchBox.tooltip({ | ||||||
| @@ -49,6 +52,8 @@ function hideSearch() { | |||||||
|  |  | ||||||
|     $searchResults.hide(); |     $searchResults.hide(); | ||||||
|     $searchBox.slideUp(); |     $searchBox.slideUp(); | ||||||
|  |  | ||||||
|  |     utils.focusSavedElement(); | ||||||
| } | } | ||||||
|  |  | ||||||
| function toggleSearch() { | function toggleSearch() { | ||||||
|   | |||||||
| @@ -635,7 +635,7 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) { | |||||||
|         extraOptions.saveSelection = false; |         extraOptions.saveSelection = false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (extraOptions.saveSelection) { |     if (extraOptions.saveSelection && utils.isCKEditorInitialized()) { | ||||||
|         [extraOptions.title, extraOptions.content] = parseSelectedHtml(window.cutToNote.getSelectedHtml()); |         [extraOptions.title, extraOptions.content] = parseSelectedHtml(window.cutToNote.getSelectedHtml()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -648,7 +648,7 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) { | |||||||
|         type: extraOptions.type |         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 |         // we remove the selection only after it was saved to server to make sure we don't lose anything | ||||||
|         window.cutToNote.removeSelection(); |         window.cutToNote.removeSelection(); | ||||||
|     } |     } | ||||||
| @@ -870,7 +870,7 @@ window.glob.cutIntoNote = () => createNoteInto(true); | |||||||
|  |  | ||||||
| keyboardActionService.setGlobalActionHandler('CutIntoNote', () => createNoteInto(true)); | keyboardActionService.setGlobalActionHandler('CutIntoNote', () => createNoteInto(true)); | ||||||
|  |  | ||||||
| keyboardActionService.setGlobalActionHandler('CreateNoteInto', createNoteInto); | keyboardActionService.setGlobalActionHandler('CreateNoteInto', () => createNoteInto(true)); | ||||||
|  |  | ||||||
| keyboardActionService.setGlobalActionHandler('ScrollToActiveNote', scrollToActiveNote); | keyboardActionService.setGlobalActionHandler('ScrollToActiveNote', scrollToActiveNote); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -62,6 +62,11 @@ class TreeContextMenu { | |||||||
|             !isHoisted || !isNotRoot ? null : { title: 'Unhoist note <kbd data-kb-action="ToggleNoteHoisting"></kbd>', cmd: "unhoist", uiIcon: "arrow-up" }, |             !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", |             { title: 'Edit branch prefix <kbd data-kb-action="EditBranchPrefix"></kbd>', cmd: "editBranchPrefix", uiIcon: "empty", | ||||||
|                 enabled: isNotRoot && parentNotSearch && noSelectedNotes}, |                 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: "----" }, | ||||||
|             { title: "Protect subtree", cmd: "protectSubtree", uiIcon: "check-shield", enabled: noSelectedNotes }, |             { title: "Protect subtree", cmd: "protectSubtree", uiIcon: "check-shield", enabled: noSelectedNotes }, | ||||||
|             { title: "Unprotect subtree", cmd: "unprotectSubtree", uiIcon: "shield", enabled: noSelectedNotes }, |             { title: "Unprotect subtree", cmd: "unprotectSubtree", uiIcon: "shield", enabled: noSelectedNotes }, | ||||||
| @@ -84,12 +89,7 @@ class TreeContextMenu { | |||||||
|             { title: "Export", cmd: "export", uiIcon: "empty", |             { title: "Export", cmd: "export", uiIcon: "empty", | ||||||
|                 enabled: notSearch && noSelectedNotes }, |                 enabled: notSearch && noSelectedNotes }, | ||||||
|             { title: "Import into note", cmd: "importIntoNote", uiIcon: "empty", |             { title: "Import into note", cmd: "importIntoNote", uiIcon: "empty", | ||||||
|                 enabled: notSearch && noSelectedNotes }, |                 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 } |  | ||||||
|                 ] }, |  | ||||||
|         ].filter(row => row !== null); |         ].filter(row => row !== null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -209,9 +209,50 @@ function getMimeTypeClass(mime) { | |||||||
| function closeActiveDialog() { | function closeActiveDialog() { | ||||||
|     if (glob.activeDialog) { |     if (glob.activeDialog) { | ||||||
|         glob.activeDialog.modal('hide'); |         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) { | function isHtmlEmpty(html) { | ||||||
|     html = html.toLowerCase(); |     html = html.toLowerCase(); | ||||||
|  |  | ||||||
| @@ -248,6 +289,10 @@ function copySelectionToClipboard() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function isCKEditorInitialized() { | ||||||
|  |     return !!(window && window.cutToNote); | ||||||
|  | } | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     reloadApp, |     reloadApp, | ||||||
|     parseDate, |     parseDate, | ||||||
| @@ -277,9 +322,13 @@ export default { | |||||||
|     getNoteTypeClass, |     getNoteTypeClass, | ||||||
|     getMimeTypeClass, |     getMimeTypeClass, | ||||||
|     closeActiveDialog, |     closeActiveDialog, | ||||||
|  |     openDialog, | ||||||
|  |     saveFocusedElement, | ||||||
|  |     focusSavedElement, | ||||||
|     isHtmlEmpty, |     isHtmlEmpty, | ||||||
|     clearBrowserCache, |     clearBrowserCache, | ||||||
|     getUrlForDownload, |     getUrlForDownload, | ||||||
|     normalizeShortcut, |     normalizeShortcut, | ||||||
|     copySelectionToClipboard |     copySelectionToClipboard, | ||||||
|  |     isCKEditorInitialized | ||||||
| }; | }; | ||||||
| @@ -71,7 +71,19 @@ class AttributesWidget extends StandardWidget { | |||||||
|     async renderAttributes(attributes, $container) { |     async renderAttributes(attributes, $container) { | ||||||
|         for (const attribute of attributes) { |         for (const attribute of attributes) { | ||||||
|             if (attribute.type === 'label') { |             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') { |             } else if (attribute.type === 'relation') { | ||||||
|                 if (attribute.value) { |                 if (attribute.value) { | ||||||
|                     $container.append('@' + attribute.name + "="); |                     $container.append('@' + attribute.name + "="); | ||||||
|   | |||||||
| @@ -897,7 +897,7 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href | |||||||
|     text-align: center; |     text-align: center; | ||||||
| } | } | ||||||
|  |  | ||||||
| .note-book-card.type-image .note-book-content img { | .note-book-card.type-image .note-book-content img, .note-book-card.type-text .note-book-content img { | ||||||
|     max-width: 100%; |     max-width: 100%; | ||||||
|     max-height: 100%; |     max-height: 100%; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
|  | const attributeService = require("../../services/attributes"); | ||||||
| const noteService = require('../../services/notes'); | const noteService = require('../../services/notes'); | ||||||
| const dateNoteService = require('../../services/date_notes'); | const dateNoteService = require('../../services/date_notes'); | ||||||
| const dateUtils = require('../../services/date_utils'); | const dateUtils = require('../../services/date_utils'); | ||||||
| @@ -23,16 +24,26 @@ async function findClippingNote(todayNote, pageUrl) { | |||||||
|     return null; |     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) { | async function addClipping(req) { | ||||||
|     const {title, content, pageUrl, images} = req.body; |     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) { |     if (!clippingNote) { | ||||||
|         clippingNote = (await noteService.createNewNote({ |         clippingNote = (await noteService.createNewNote({ | ||||||
|             parentNoteId: todayNote.noteId, |             parentNoteId: clipperInbox.noteId, | ||||||
|             title: title, |             title: title, | ||||||
|             content: '', |             content: '', | ||||||
|             type: 'text' |             type: 'text' | ||||||
| @@ -54,10 +65,10 @@ async function addClipping(req) { | |||||||
| async function createNote(req) { | async function createNote(req) { | ||||||
|     const {title, content, pageUrl, images, clipType} = req.body; |     const {title, content, pageUrl, images, clipType} = req.body; | ||||||
|  |  | ||||||
|     const todayNote = await dateNoteService.getDateNote(dateUtils.localNowDate()); |     const clipperInbox = await getClipperInboxNote(); | ||||||
|  |  | ||||||
|     const {note} = await noteService.createNewNote({ |     const {note} = await noteService.createNewNote({ | ||||||
|         parentNoteId: todayNote.noteId, |         parentNoteId: clipperInbox.noteId, | ||||||
|         title, |         title, | ||||||
|         content, |         content, | ||||||
|         type: 'text' |         type: 'text' | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| module.exports = { buildDate:"2020-02-01T10:17:51+01:00", buildRevision: "0f25c8a95f381d99b66735b9c0af3e319edb72ed" }; | module.exports = { buildDate:"2020-02-24T22:59:22+01:00", buildRevision: "fb55cdaea6b1367129e11118b8b6fd2eadebad5f" }; | ||||||
|   | |||||||
| @@ -323,14 +323,25 @@ class ConsistencyChecks { | |||||||
|                     WHERE isErased = 1 |                     WHERE isErased = 1 | ||||||
|                       AND content IS NOT NULL`, |                       AND content IS NOT NULL`, | ||||||
|             async ({noteId}) => { |             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`); |             // we always fix this issue because there does not seem to be a good way to prevent it. | ||||||
|             } |             // Scenario in which this can happen: | ||||||
|             else { |             // 1. user on instance A deletes the note (sync for notes is created, but not for note_contents) and is later erased | ||||||
|                 logError(`Note ${noteId} content is not null even though the note is 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(` |         await this.findAndFixIssues(` | ||||||
| @@ -547,23 +558,23 @@ class ConsistencyChecks { | |||||||
|             }); |             }); | ||||||
|  |  | ||||||
|         await this.findAndFixIssues(` |         await this.findAndFixIssues(` | ||||||
|         SELECT  |             SELECT  | ||||||
|           id, entityId |               id, entityId | ||||||
|         FROM  |             FROM  | ||||||
|           sync  |               sync  | ||||||
|           LEFT JOIN ${entityName} ON entityId = ${key}  |               LEFT JOIN ${entityName} ON entityId = ${key}  | ||||||
|         WHERE  |             WHERE  | ||||||
|           sync.entityName = '${entityName}'  |               sync.entityName = '${entityName}'  | ||||||
|           AND ${key} IS NULL`, |               AND ${key} IS NULL`, | ||||||
|             async ({id, entityId}) => { |                 async ({id, entityId}) => { | ||||||
|                 if (this.autoFix) { |                     if (this.autoFix) { | ||||||
|                     await sql.execute("DELETE FROM sync WHERE entityName = ? AND entityId = ?", [entityName, entityId]); |                         await sql.execute("DELETE FROM sync WHERE entityName = ? AND entityId = ?", [entityName, entityId]); | ||||||
|  |  | ||||||
|                     logFix(`Deleted extra sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); |                         logFix(`Deleted extra sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); | ||||||
|                 } else { |                     } else { | ||||||
|                     logError(`Unrecognized sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); |                         logError(`Unrecognized sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); | ||||||
|                 } |                     } | ||||||
|             }); |                 }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async findSyncRowsIssues() { |     async findSyncRowsIssues() { | ||||||
|   | |||||||
| @@ -152,6 +152,11 @@ async function importTar(taskContext, fileBuffer, importRootNote) { | |||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             if (attr.type === 'label' && attr.name === 'externalLink') { | ||||||
|  |                 // also created automatically | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             if (attr.type === 'relation') { |             if (attr.type === 'relation') { | ||||||
|                 attr.value = getNewNoteId(attr.value); |                 attr.value = getNewNoteId(attr.value); | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -242,6 +242,20 @@ function findInternalLinks(content, foundLinks) { | |||||||
|     return content.replace(/href="[^"]*#root/g, 'href="#root'); |     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) { | function findIncludeNoteLinks(content, foundLinks) { | ||||||
|     const re = /<section class="include-note" data-note-id="([a-zA-Z0-9]+)">/g; |     const re = /<section class="include-note" data-note-id="([a-zA-Z0-9]+)">/g; | ||||||
|     let match; |     let match; | ||||||
| @@ -281,6 +295,7 @@ async function saveLinks(note, content) { | |||||||
|     if (note.type === 'text') { |     if (note.type === 'text') { | ||||||
|         content = findImageLinks(content, foundLinks); |         content = findImageLinks(content, foundLinks); | ||||||
|         content = findInternalLinks(content, foundLinks); |         content = findInternalLinks(content, foundLinks); | ||||||
|  |         content = findExternalLinks(content, foundLinks); | ||||||
|         content = findIncludeNoteLinks(content, foundLinks); |         content = findIncludeNoteLinks(content, foundLinks); | ||||||
|     } |     } | ||||||
|     else if (note.type === 'relation-map') { |     else if (note.type === 'relation-map') { | ||||||
| @@ -293,9 +308,11 @@ async function saveLinks(note, content) { | |||||||
|     const existingLinks = await note.getLinks(); |     const existingLinks = await note.getLinks(); | ||||||
|  |  | ||||||
|     for (const foundLink of foundLinks) { |     for (const foundLink of foundLinks) { | ||||||
|         const targetNote = await repository.getNote(foundLink.value); |         if (foundLink.name !== 'externalLink') { | ||||||
|         if (!targetNote || targetNote.isDeleted) { |             const targetNote = await repository.getNote(foundLink.value); | ||||||
|             continue; |             if (!targetNote || targetNote.isDeleted) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const existingLink = existingLinks.find(existingLink => |         const existingLink = existingLinks.find(existingLink => | ||||||
| @@ -305,7 +322,7 @@ async function saveLinks(note, content) { | |||||||
|         if (!existingLink) { |         if (!existingLink) { | ||||||
|             await new Attribute({ |             await new Attribute({ | ||||||
|                 noteId: note.noteId, |                 noteId: note.noteId, | ||||||
|                 type: 'relation', |                 type: foundLink.name === 'externalLink' ? 'label' : 'relation', | ||||||
|                 name: foundLink.name, |                 name: foundLink.name, | ||||||
|                 value: foundLink.value, |                 value: foundLink.value, | ||||||
|             }).save(); |             }).save(); | ||||||
| @@ -582,6 +599,7 @@ async function eraseDeletedNotes() { | |||||||
|         UPDATE notes  |         UPDATE notes  | ||||||
|         SET title = '[deleted]', |         SET title = '[deleted]', | ||||||
|             contentLength = 0, |             contentLength = 0, | ||||||
|  |             isProtected = 0, | ||||||
|             isErased = 1 |             isErased = 1 | ||||||
|         WHERE noteId IN (???)`, noteIdsToErase); |         WHERE noteId IN (???)`, noteIdsToErase); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -228,9 +228,10 @@ async function syncFinished(syncContext) { | |||||||
|  |  | ||||||
| async function checkContentHash(syncContext) { | async function checkContentHash(syncContext) { | ||||||
|     const resp = await syncRequest(syncContext, 'GET', '/api/sync/check'); |     const resp = await syncRequest(syncContext, 'GET', '/api/sync/check'); | ||||||
|  |     const lastSyncedPullId = await getLastSyncedPull(); | ||||||
|  |  | ||||||
|     if (await getLastSyncedPull() < resp.maxSyncId) { |     if (lastSyncedPullId < resp.maxSyncId) { | ||||||
|         log.info("There are some outstanding pulls, skipping content check."); |         log.info(`There are some outstanding pulls (${lastSyncedPullId} vs. ${resp.maxSyncId}), skipping content check.`); | ||||||
|  |  | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -73,6 +73,8 @@ | |||||||
|                         <% include details/relation_map.ejs %> |                         <% include details/relation_map.ejs %> | ||||||
|  |  | ||||||
|                         <% include details/protected_session_password.ejs %> |                         <% include details/protected_session_password.ejs %> | ||||||
|  |  | ||||||
|  |                         <% include details/book.ejs %> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|   | |||||||
| @@ -19,13 +19,13 @@ | |||||||
|                 <label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupType"> |                 <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> |                     I'm a new user and I want to create new Trilium document for my notes</label> | ||||||
|             </div> |             </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"> |                 <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> |                     I have desktop instance already and I want to setup sync with it</label> | ||||||
|             </div> |             </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"> |                 <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> |             </div> | ||||||
|  |  | ||||||
|             <button type="button" data-bind="disable: !setupTypeSelected(), click: selectSetupType" class="btn btn-primary">Next</button> |             <button type="button" data-bind="disable: !setupTypeSelected(), click: selectSetupType" class="btn btn-primary">Next</button> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user