mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	Compare commits
	
		
			10 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 38e7649ac3 | ||
|  | 7a2c7edd7e | ||
|  | cfb850acb2 | ||
|  | a16aaf7a81 | ||
|  | 522f71cb91 | ||
|  | d357943ebb | ||
|  | 07043fb177 | ||
|  | 1f8d382b1f | ||
|  | 61e8cbbcba | ||
|  | 86c5dd6494 | 
							
								
								
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "trilium", | ||||
|   "version": "0.36.5", | ||||
|   "version": "0.37.3", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|   "name": "trilium", | ||||
|   "productName": "Trilium Notes", | ||||
|   "description": "Trilium Notes", | ||||
|   "version": "0.37.2", | ||||
|   "version": "0.37.4", | ||||
|   "license": "AGPL-3.0-only", | ||||
|   "main": "electron.js", | ||||
|   "bin": { | ||||
|   | ||||
| @@ -74,7 +74,7 @@ $form.on('submit', () => { | ||||
| function exportBranch(branchId, type, format, version) { | ||||
|     taskId = utils.randomString(10); | ||||
|  | ||||
|     const url = utils.getHost() + `/api/notes/${branchId}/export/${type}/${format}/${version}/${taskId}`; | ||||
|     const url = utils.getUrlForDownload(`api/notes/${branchId}/export/${type}/${format}/${version}/${taskId}`); | ||||
|  | ||||
|     utils.download(url); | ||||
| } | ||||
|   | ||||
| @@ -102,7 +102,9 @@ async function setContentPane() { | ||||
|     const $downloadButton = $('<button class="btn btn-sm btn-primary" type="button">Download</button>'); | ||||
|  | ||||
|     $downloadButton.on('click', () => { | ||||
|         utils.download(utils.getHost() + `/api/notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}/download`); | ||||
|         const url = utils.getUrlForDownload(`api/notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}/download`); | ||||
|  | ||||
|         utils.download(url); | ||||
|     }); | ||||
|  | ||||
|     $titleButtons.append($downloadButton); | ||||
|   | ||||
| @@ -4,83 +4,97 @@ import cloningService from "./cloning.js"; | ||||
| import toastService from "./toast.js"; | ||||
| import hoistedNoteService from "./hoisted_note.js"; | ||||
|  | ||||
| let clipboardIds = []; | ||||
| /* | ||||
|  * Clipboard contains node keys which are not stable. If a (part of the) tree is reloaded, | ||||
|  * node keys in the clipboard might not exist anymore. Code here should then be ready to deal | ||||
|  * with this. | ||||
|  */ | ||||
|  | ||||
| let clipboardNodeKeys = []; | ||||
| let clipboardMode = null; | ||||
|  | ||||
| async function pasteAfter(node) { | ||||
| async function pasteAfter(afterNode) { | ||||
|     if (isClipboardEmpty()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (clipboardMode === 'cut') { | ||||
|         const nodes = clipboardIds.map(nodeKey => treeUtils.getNodeByKey(nodeKey)); | ||||
|         const nodes = clipboardNodeKeys.map(nodeKey => treeUtils.getNodeByKey(nodeKey)); | ||||
|  | ||||
|         await treeChangesService.moveAfterNode(nodes, node); | ||||
|         await treeChangesService.moveAfterNode(nodes, afterNode); | ||||
|  | ||||
|         clipboardIds = []; | ||||
|         clipboardNodeKeys = []; | ||||
|         clipboardMode = null; | ||||
|     } | ||||
|     else if (clipboardMode === 'copy') { | ||||
|         for (const noteId of clipboardIds) { | ||||
|             await cloningService.cloneNoteAfter(noteId, node.data.branchId); | ||||
|         for (const nodeKey of clipboardNodeKeys) { | ||||
|             const clipNode = treeUtils.getNodeByKey(nodeKey); | ||||
|  | ||||
|             await cloningService.cloneNoteAfter(clipNode.data.noteId, afterNode.data.branchId); | ||||
|         } | ||||
|  | ||||
|         // copy will keep clipboardIds and clipboardMode so it's possible to paste into multiple places | ||||
|     } | ||||
|     else if (clipboardIds.length === 0) { | ||||
|         // just do nothing | ||||
|     } | ||||
|     else { | ||||
|         toastService.throwError("Unrecognized clipboard mode=" + clipboardMode); | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function pasteInto(node) { | ||||
| async function pasteInto(parentNode) { | ||||
|     if (isClipboardEmpty()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (clipboardMode === 'cut') { | ||||
|         const nodes = clipboardIds.map(nodeKey => treeUtils.getNodeByKey(nodeKey)); | ||||
|         const nodes = clipboardNodeKeys.map(nodeKey => treeUtils.getNodeByKey(nodeKey)); | ||||
|  | ||||
|         await treeChangesService.moveToNode(nodes, node); | ||||
|         await treeChangesService.moveToNode(nodes, parentNode); | ||||
|  | ||||
|         await node.setExpanded(true); | ||||
|         await parentNode.setExpanded(true); | ||||
|  | ||||
|         clipboardIds = []; | ||||
|         clipboardNodeKeys = []; | ||||
|         clipboardMode = null; | ||||
|     } | ||||
|     else if (clipboardMode === 'copy') { | ||||
|         for (const noteId of clipboardIds) { | ||||
|             await cloningService.cloneNoteTo(noteId, node.data.noteId); | ||||
|         for (const nodeKey of clipboardNodeKeys) { | ||||
|             const clipNode = treeUtils.getNodeByKey(nodeKey); | ||||
|  | ||||
|             await cloningService.cloneNoteTo(clipNode.data.noteId, parentNode.data.noteId); | ||||
|         } | ||||
|  | ||||
|         await node.setExpanded(true); | ||||
|         await parentNode.setExpanded(true); | ||||
|  | ||||
|         // copy will keep clipboardIds and clipboardMode so it's possible to paste into multiple places | ||||
|     } | ||||
|     else if (clipboardIds.length === 0) { | ||||
|         // just do nothing | ||||
|     } | ||||
|     else { | ||||
|         toastService.throwError("Unrecognized clipboard mode=" + mode); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function copy(nodes) { | ||||
|     clipboardIds = nodes.map(node => node.data.noteId); | ||||
|     clipboardNodeKeys = nodes.map(node => node.key); | ||||
|     clipboardMode = 'copy'; | ||||
|  | ||||
|     toastService.showMessage("Note(s) have been copied into clipboard."); | ||||
| } | ||||
|  | ||||
| function cut(nodes) { | ||||
|     clipboardIds = nodes | ||||
|     clipboardNodeKeys = nodes | ||||
|         .filter(node => node.data.noteId !== hoistedNoteService.getHoistedNoteNoPromise()) | ||||
|         .filter(node => node.getParent().data.noteType !== 'search') | ||||
|         .map(node => node.data.noteId); | ||||
|         .map(node => node.key); | ||||
|  | ||||
|     if (clipboardIds.length > 0) { | ||||
|     if (clipboardNodeKeys.length > 0) { | ||||
|         clipboardMode = 'cut'; | ||||
|  | ||||
|         toastService.showMessage("Note(s) have been cut into clipboard."); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function isEmpty() { | ||||
|     return clipboardIds.length === 0; | ||||
| function isClipboardEmpty() { | ||||
|     clipboardNodeKeys = clipboardNodeKeys.filter(key => !!treeUtils.getNodeByKey(key)); | ||||
|  | ||||
|     return clipboardNodeKeys.length === 0; | ||||
| } | ||||
|  | ||||
| export default { | ||||
| @@ -88,5 +102,5 @@ export default { | ||||
|     pasteInto, | ||||
|     cut, | ||||
|     copy, | ||||
|     isEmpty | ||||
|     isClipboardEmpty | ||||
| } | ||||
| @@ -185,8 +185,7 @@ class NoteDetailBook { | ||||
|         } | ||||
|         else if (type === 'file') { | ||||
|             function getFileUrl() { | ||||
|                 // electron needs absolute URL so we extract current host, port, protocol | ||||
|                 return utils.getHost() + "/api/notes/" + note.noteId + "/download"; | ||||
|                 return utils.getUrlForDownload("api/notes/" + note.noteId + "/download"); | ||||
|             } | ||||
|  | ||||
|             const $downloadButton = $('<button class="file-download btn btn-primary" type="button">Download</button>'); | ||||
|   | ||||
| @@ -87,8 +87,7 @@ class NoteDetailFile { | ||||
|     } | ||||
|  | ||||
|     getFileUrl() { | ||||
|         // electron needs absolute URL so we extract current host, port, protocol | ||||
|         return utils.getHost() + "/api/notes/" + this.ctx.note.noteId + "/download"; | ||||
|         return utils.getUrlForDownload("api/notes/" + this.ctx.note.noteId + "/download"); | ||||
|     } | ||||
|  | ||||
|     show() {} | ||||
|   | ||||
| @@ -98,8 +98,7 @@ class NoteDetailImage { | ||||
|     } | ||||
|  | ||||
|     getFileUrl() { | ||||
|         // electron needs absolute URL so we extract current host, port, protocol | ||||
|         return utils.getHost() + `/api/notes/${this.ctx.note.noteId}/download`; | ||||
|         return utils.getUrlForDownload(`api/notes/${this.ctx.note.noteId}/download`); | ||||
|     } | ||||
|  | ||||
|     show() {} | ||||
|   | ||||
| @@ -142,6 +142,12 @@ async function refreshSearch() { | ||||
|     toastService.showMessage("Saved search note refreshed."); | ||||
| } | ||||
|  | ||||
| function searchInSubtree(noteId) { | ||||
|     showSearch(); | ||||
|  | ||||
|     $searchInput.val(`@in=${noteId} @text*=*`); | ||||
| } | ||||
|  | ||||
| function init() { | ||||
|     const hashValue = document.location.hash ? document.location.hash.substr(1) : ""; // strip initial # | ||||
|  | ||||
| @@ -178,5 +184,6 @@ export default { | ||||
|     refreshSearch, | ||||
|     doSearch, | ||||
|     init, | ||||
|     searchInSubtree, | ||||
|     getHelpText: () => helpText | ||||
| }; | ||||
| @@ -9,6 +9,7 @@ import hoistedNoteService from './hoisted_note.js'; | ||||
| import noteDetailService from './note_detail.js'; | ||||
| import clipboard from './clipboard.js'; | ||||
| import protectedSessionHolder from "./protected_session_holder.js"; | ||||
| import searchNotesService from "./search_notes.js"; | ||||
|  | ||||
| class TreeContextMenu { | ||||
|     constructor(node) { | ||||
| @@ -55,6 +56,8 @@ class TreeContextMenu { | ||||
|             { title: "Delete <kbd>Delete</kbd>", cmd: "delete", uiIcon: "trash", | ||||
|                 enabled: isNotRoot && !isHoisted && parentNotSearch }, | ||||
|             { title: "----" }, | ||||
|             { title: "Search in subtree <kbd>Ctrl+Shift+S</kbd>", cmd: "searchInSubtree", uiIcon: "search", | ||||
|                 enabled: notSearch && noSelectedNotes }, | ||||
|             isHoisted ? null : { title: "Hoist note <kbd>Ctrl-H</kbd>", cmd: "hoist", uiIcon: "empty", enabled: noSelectedNotes && notSearch }, | ||||
|             !isHoisted || !isNotRoot ? null : { title: "Unhoist note <kbd>Ctrl-H</kbd>", cmd: "unhoist", uiIcon: "arrow-up" }, | ||||
|             { title: "Edit branch prefix <kbd>F2</kbd>", cmd: "editBranchPrefix", uiIcon: "empty", | ||||
| @@ -72,9 +75,9 @@ class TreeContextMenu { | ||||
|             { title: "Move to ... <kbd>Ctrl+Shift+X</kbd>", cmd: "moveTo", uiIcon: "empty", | ||||
|                 enabled: isNotRoot && !isHoisted && parentNotSearch }, | ||||
|             { title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "paste", | ||||
|                 enabled: !clipboard.isEmpty() && notSearch && noSelectedNotes }, | ||||
|                 enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes }, | ||||
|             { title: "Paste after", cmd: "pasteAfter", uiIcon: "paste", | ||||
|                 enabled: !clipboard.isEmpty() && isNotRoot && parentNotSearch && noSelectedNotes }, | ||||
|                 enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes }, | ||||
|             { title: "Duplicate note here", cmd: "duplicateNote", uiIcon: "empty", | ||||
|                 enabled: noSelectedNotes && parentNotSearch && (!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) }, | ||||
|             { title: "----" }, | ||||
| @@ -177,6 +180,9 @@ class TreeContextMenu { | ||||
|  | ||||
|             treeService.duplicateNote(this.node.data.noteId, branch.parentNoteId); | ||||
|         } | ||||
|         else if (cmd === "searchInSubtree") { | ||||
|             searchNotesService.searchInSubtree(this.node.data.noteId); | ||||
|         } | ||||
|         else { | ||||
|             ws.logError("Unknown command: " + cmd); | ||||
|         } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import treeService from "./tree.js"; | ||||
| import hoistedNoteService from "./hoisted_note.js"; | ||||
| import clipboard from "./clipboard.js"; | ||||
| import treeCache from "./tree_cache.js"; | ||||
| import searchNoteService from "./search_notes.js"; | ||||
|  | ||||
| const keyBindings = { | ||||
|     "del": node => { | ||||
| @@ -167,6 +168,11 @@ const keyBindings = { | ||||
|     "down": node => { | ||||
|         node.navigate($.ui.keyCode.DOWN, true).then(treeService.clearSelectedNodes); | ||||
|  | ||||
|         return false; | ||||
|     }, | ||||
|     "ctrl+shift+s": node => { | ||||
|         searchNoteService.searchInSubtree(node.data.noteId); | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -214,6 +214,20 @@ async function clearBrowserCache() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param url - should be without initial slash!!! | ||||
|  */ | ||||
| function getUrlForDownload(url) { | ||||
|     if (isElectron()) { | ||||
|         // electron needs absolute URL so we extract current host, port, protocol | ||||
|         return getHost() + '/' + url; | ||||
|     } | ||||
|     else { | ||||
|         // web server can be deployed on subdomain so we need to use relative path | ||||
|         return url; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default { | ||||
|     reloadApp, | ||||
|     parseDate, | ||||
| @@ -230,7 +244,6 @@ export default { | ||||
|     escapeHtml, | ||||
|     stopWatch, | ||||
|     formatLabel, | ||||
|     getHost, | ||||
|     download, | ||||
|     toObject, | ||||
|     randomString, | ||||
| @@ -245,5 +258,6 @@ export default { | ||||
|     getMimeTypeClass, | ||||
|     closeActiveDialog, | ||||
|     isHtmlEmpty, | ||||
|     clearBrowserCache | ||||
|     clearBrowserCache, | ||||
|     getUrlForDownload | ||||
| }; | ||||
| @@ -76,12 +76,12 @@ function SetupModel() { | ||||
|             } | ||||
|  | ||||
|             // not using server.js because it loads too many dependencies | ||||
|             $.post('/api/setup/new-document', { | ||||
|             $.post('api/setup/new-document', { | ||||
|                 username: username, | ||||
|                 password: password1, | ||||
|                 theme: theme | ||||
|             }).then(() => { | ||||
|                 window.location.replace("/"); | ||||
|                 window.location.replace("./"); | ||||
|             }); | ||||
|         } | ||||
|         else if (this.setupType() === 'sync-from-server') { | ||||
| @@ -128,10 +128,10 @@ function SetupModel() { | ||||
| } | ||||
|  | ||||
| async function checkOutstandingSyncs() { | ||||
|     const { stats, initialized } = await $.get('/api/sync/stats'); | ||||
|     const { stats, initialized } = await $.get('api/sync/stats'); | ||||
|  | ||||
|     if (initialized) { | ||||
|         window.location.replace("/"); | ||||
|         window.location.replace("./"); | ||||
|     } | ||||
|  | ||||
|     const totalOutstandingSyncs = stats.outstandingPushes + stats.outstandingPulls; | ||||
|   | ||||
| @@ -18,7 +18,7 @@ async function getRecentChanges() { | ||||
|                 note_revisions | ||||
|                 JOIN notes USING(noteId) | ||||
|             ORDER BY | ||||
|                 utcDateCreated DESC | ||||
|                 note_revisions.utcDateCreated DESC | ||||
|             LIMIT 1000 | ||||
|         ) | ||||
|         UNION ALL SELECT * FROM ( | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| module.exports = { buildDate:"2019-11-18T23:04:09+01:00", buildRevision: "834e1f7253186922d2b5df2f6a01c34f7c2d7fe4" }; | ||||
| module.exports = { buildDate:"2019-11-22T22:38:03+01:00", buildRevision: "7a2c7edd7e9b975bf64f732629e711379baecf48" }; | ||||
|   | ||||
| @@ -56,6 +56,8 @@ async function checkContentHashes(otherHashes) { | ||||
|         if (hashes[key] !== otherHashes[key]) { | ||||
|             allChecksPassed = false; | ||||
|  | ||||
|             log.info(`Content hash check for ${key} FAILED. Local is ${hashes[key]}, remote is ${otherHashes[key]}`); | ||||
|  | ||||
|             if (key !== 'recent_notes') { | ||||
|                 // let's not get alarmed about recent notes which get updated often and can cause failures in race conditions | ||||
|                 ws.sendMessageToAllClients({type: 'sync-hash-check-failed'}); | ||||
|   | ||||
| @@ -35,9 +35,9 @@ async function searchForNoteIds(searchString) { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         const isInFilter = filters.find(filter => filter.name.toLowerCase() === 'in'); | ||||
|         const isInFilters = filters.filter(filter => filter.name.toLowerCase() === 'in'); | ||||
|  | ||||
|         if (isInFilter) { | ||||
|         for (const isInFilter of isInFilters) { | ||||
|             if (isInFilter.operator === '=') { | ||||
|                 noteIds = noteIds.filter(noteId => noteCacheService.isInAncestor(noteId, isInFilter.value)); | ||||
|             } | ||||
|   | ||||
| @@ -138,7 +138,6 @@ | ||||
| </div> | ||||
|  | ||||
| <script type="text/javascript"> | ||||
|     const baseApiUrl = 'api/'; | ||||
|     const glob = { | ||||
|         sourceId: '' | ||||
|     }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user