mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 03:29:02 +01:00 
			
		
		
		
	refactoring clipboard WIP
This commit is contained in:
		
							parent
							
								
									0f8a7bad06
								
							
						
					
					
						commit
						d1f679ab90
					
				| @ -34,7 +34,7 @@ import keyboardActionService from "./services/keyboard_actions.js"; | |||||||
| import splitService from "./services/split.js"; | import splitService from "./services/split.js"; | ||||||
| import optionService from "./services/options.js"; | import optionService from "./services/options.js"; | ||||||
| import noteContentRenderer from "./services/note_content_renderer.js"; | import noteContentRenderer from "./services/note_content_renderer.js"; | ||||||
| import AppContext from "./services/app_context.js"; | import appContext from "./services/app_context.js"; | ||||||
| 
 | 
 | ||||||
| window.glob.isDesktop = utils.isDesktop; | window.glob.isDesktop = utils.isDesktop; | ||||||
| window.glob.isMobile = utils.isMobile; | window.glob.isMobile = utils.isMobile; | ||||||
| @ -183,7 +183,6 @@ macInit.init(); | |||||||
| 
 | 
 | ||||||
| searchNotesService.init(); // should be in front of treeService since that one manipulates address bar hash
 | searchNotesService.init(); // should be in front of treeService since that one manipulates address bar hash
 | ||||||
| 
 | 
 | ||||||
| const appContext = new AppContext(); |  | ||||||
| appContext.showWidgets(); | appContext.showWidgets(); | ||||||
| 
 | 
 | ||||||
| entrypoints.registerEntrypoints(); | entrypoints.registerEntrypoints(); | ||||||
|  | |||||||
| @ -21,6 +21,11 @@ class Branch { | |||||||
|         return this.treeCache.getNote(this.noteId); |         return this.treeCache.getNote(this.noteId); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** @returns {NoteShort} */ | ||||||
|  |     async getParentNote() { | ||||||
|  |         return this.treeCache.getNote(this.parentNoteId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** @returns {boolean} true if it's top level, meaning its parent is root note */ |     /** @returns {boolean} true if it's top level, meaning its parent is root note */ | ||||||
|     isTopLevel() { |     isTopLevel() { | ||||||
|         return this.parentNoteId === 'root'; |         return this.parentNoteId === 'root'; | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ import SearchBoxWidget from "../widgets/search_box.js"; | |||||||
| import SearchResultsWidget from "../widgets/search_results.js"; | import SearchResultsWidget from "../widgets/search_results.js"; | ||||||
| import NoteTreeWidget from "../widgets/note_tree.js"; | import NoteTreeWidget from "../widgets/note_tree.js"; | ||||||
| 
 | 
 | ||||||
| export default class AppContext { | class AppContext { | ||||||
|     constructor() { |     constructor() { | ||||||
|         this.widgets = []; |         this.widgets = []; | ||||||
|     } |     } | ||||||
| @ -31,3 +31,7 @@ export default class AppContext { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | const appContext = new AppContext(); | ||||||
|  | 
 | ||||||
|  | export default appContext; | ||||||
| @ -8,76 +8,63 @@ import hoistedNoteService from "./hoisted_note.js"; | |||||||
| import noteDetailService from "./note_detail.js"; | import noteDetailService from "./note_detail.js"; | ||||||
| import ws from "./ws.js"; | import ws from "./ws.js"; | ||||||
| 
 | 
 | ||||||
| async function moveBeforeNode(nodesToMove, beforeNode) { | async function moveBeforeNode(branchIdsToMove, beforeBranchId) { | ||||||
|     nodesToMove = await filterRootNote(nodesToMove); |     branchIdsToMove = await filterRootNote(branchIdsToMove); | ||||||
| 
 | 
 | ||||||
|     if (beforeNode.data.noteId === 'root') { |     if (beforeBranchId === 'root') { | ||||||
|         alert('Cannot move notes before root note.'); |         alert('Cannot move notes before root note.'); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for (const nodeToMove of nodesToMove) { |     for (const branchIdToMove of branchIdsToMove) { | ||||||
|         const resp = await server.put('branches/' + nodeToMove.data.branchId + '/move-before/' + beforeNode.data.branchId); |         const resp = await server.put(`branches/${branchIdToMove}/move-before/${beforeBranchId}`); | ||||||
| 
 | 
 | ||||||
|         if (!resp.success) { |         if (!resp.success) { | ||||||
|             alert(resp.message); |             alert(resp.message); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         await changeNode( |  | ||||||
|             node => node.moveTo(beforeNode, 'before'), |  | ||||||
|             nodeToMove); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function moveAfterNode(nodesToMove, afterNode) { | async function moveAfterNode(branchIdsToMove, afterBranchId) { | ||||||
|     nodesToMove = await filterRootNote(nodesToMove); |     branchIdsToMove = await filterRootNote(branchIdsToMove); | ||||||
| 
 | 
 | ||||||
|     if (afterNode.data.noteId === 'root' || afterNode.data.noteId === await hoistedNoteService.getHoistedNoteId()) { |     const afterNote = await treeCache.getBranch(afterBranchId).getNote(); | ||||||
|  | 
 | ||||||
|  |     if (afterNote.noteId === 'root' || afterNote.noteId === await hoistedNoteService.getHoistedNoteId()) { | ||||||
|         alert('Cannot move notes after root note.'); |         alert('Cannot move notes after root note.'); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     nodesToMove.reverse(); // need to reverse to keep the note order
 |     branchIdsToMove.reverse(); // need to reverse to keep the note order
 | ||||||
| 
 | 
 | ||||||
|     for (const nodeToMove of nodesToMove) { |     for (const branchIdToMove of branchIdsToMove) { | ||||||
|         const resp = await server.put('branches/' + nodeToMove.data.branchId + '/move-after/' + afterNode.data.branchId); |         const resp = await server.put(`branches/${branchIdToMove}/move-after/${afterBranchId}`); | ||||||
| 
 | 
 | ||||||
|         if (!resp.success) { |         if (!resp.success) { | ||||||
|             alert(resp.message); |             alert(resp.message); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         await changeNode( |  | ||||||
|             node => node.moveTo(afterNode, 'after'), |  | ||||||
|             nodeToMove); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function moveToNode(nodesToMove, toNode) { | async function moveToNode(branchIdsToMove, newParentNoteId) { | ||||||
|     nodesToMove = await filterRootNote(nodesToMove); |     branchIdsToMove = await filterRootNote(branchIdsToMove); | ||||||
| 
 | 
 | ||||||
|     for (const nodeToMove of nodesToMove) { |     for (const branchIdToMove of branchIdsToMove) { | ||||||
|         if (nodeToMove.data.noteId === await hoistedNoteService.getHoistedNoteId() |         const branchToMove = treeCache.getBranch(branchIdToMove); | ||||||
|             || nodeToMove.getParent().data.noteType === 'search') { | 
 | ||||||
|  |         if (branchToMove.noteId === await hoistedNoteService.getHoistedNoteId() | ||||||
|  |             || (await branchToMove.getParentNote()).type === 'search') { | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const resp = await server.put('branches/' + nodeToMove.data.branchId + '/move-to/' + toNode.data.noteId); |         const resp = await server.put(`branches/${branchIdToMove}/move-to/${newParentNoteId}`); | ||||||
| 
 | 
 | ||||||
|         if (!resp.success) { |         if (!resp.success) { | ||||||
|             alert(resp.message); |             alert(resp.message); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         await changeNode(async node => { |  | ||||||
|                 // first expand which will force lazy load and only then move the node
 |  | ||||||
|                 // if this is not expanded before moving, then lazy load won't happen because it already contains node
 |  | ||||||
|                 // this doesn't work if this isn't a folder yet, that's why we expand second time below
 |  | ||||||
|                 await toNode.setExpanded(true); |  | ||||||
| 
 |  | ||||||
|                 node.moveTo(toNode); |  | ||||||
|             }, nodeToMove); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -170,72 +157,21 @@ async function moveNodeUpInHierarchy(node) { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!hoistedNoteService.isTopLevelNode(node) && node.getParent().getChildren().length <= 1) { |     if (!await hoistedNoteService.isTopLevelNode(node) && node.getParent().getChildren().length <= 1) { | ||||||
|         node.getParent().folder = false; |         node.getParent().folder = false; | ||||||
|         node.getParent().renderTitle(); |         node.getParent().renderTitle(); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     await changeNode( |  | ||||||
|         node => node.moveTo(node.getParent(), 'after'), |  | ||||||
|         node); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function changeNode(func, node) { | async function filterRootNote(branchIds) { | ||||||
|     utils.assertArguments(func, node); |  | ||||||
| 
 |  | ||||||
|     const childNoteId = node.data.noteId; |  | ||||||
|     const thisOldParentNode = node.getParent(); |  | ||||||
| 
 |  | ||||||
|     // this will move the node the user is directly operating on to the desired location
 |  | ||||||
|     // note that there might be other instances of this note in the tree and those are updated through
 |  | ||||||
|     // force reloading. We could simplify our lives by just reloading this one as well, but that leads
 |  | ||||||
|     // to flickering and not good user experience. Current solution leads to no-flicker experience in most
 |  | ||||||
|     // cases (since cloning is not used that often) and correct for multi-clone use cases
 |  | ||||||
|     await func(node); |  | ||||||
| 
 |  | ||||||
|     const thisNewParentNode = node.getParent(); |  | ||||||
| 
 |  | ||||||
|     node.data.parentNoteId = thisNewParentNode.data.noteId; |  | ||||||
| 
 |  | ||||||
|     await treeCache.reloadNotes([childNoteId]); |  | ||||||
| 
 |  | ||||||
|     await treeService.checkFolderStatus(thisOldParentNode); |  | ||||||
|     await treeService.checkFolderStatus(thisNewParentNode); |  | ||||||
| 
 |  | ||||||
|     if (!thisNewParentNode.isExpanded()) { |  | ||||||
|         // this expands the note in case it become the folder only after the move
 |  | ||||||
|         await thisNewParentNode.setExpanded(true); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for (const newParentNode of treeService.getNodesByNoteId(thisNewParentNode.data.noteId)) { |  | ||||||
|         if (newParentNode.key === thisNewParentNode.key) { |  | ||||||
|             // this one has been handled above specifically
 |  | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         newParentNode.load(true); // force reload to show up new note
 |  | ||||||
| 
 |  | ||||||
|         await treeService.checkFolderStatus(newParentNode); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for (const oldParentNode of treeService.getNodesByNoteId(thisOldParentNode.data.noteId)) { |  | ||||||
|         if (oldParentNode.key === thisOldParentNode.key) { |  | ||||||
|             // this one has been handled above specifically
 |  | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         await oldParentNode.load(true); // force reload to show up new note
 |  | ||||||
| 
 |  | ||||||
|         await treeService.checkFolderStatus(oldParentNode); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async function filterRootNote(nodes) { |  | ||||||
|     const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); |     const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); | ||||||
| 
 | 
 | ||||||
|     return nodes.filter(node => |     return branchIds.filter(branchId => { | ||||||
|         node.data.noteId !== 'root' |        const branch = treeCache.getBranch(branchId); | ||||||
|         && node.data.noteId !== hoistedNoteId); | 
 | ||||||
|  |         return branch.noteId !== 'root' | ||||||
|  |             && branch.noteId !== hoistedNoteId; | ||||||
|  |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function makeToast(id, message) { | function makeToast(id, message) { | ||||||
|  | |||||||
| @ -1,70 +1,60 @@ | |||||||
| import treeService from "./tree.js"; |  | ||||||
| import treeChangesService from "./branches.js"; | import treeChangesService from "./branches.js"; | ||||||
| import cloningService from "./cloning.js"; | import cloningService from "./cloning.js"; | ||||||
| import toastService from "./toast.js"; | import toastService from "./toast.js"; | ||||||
| import hoistedNoteService from "./hoisted_note.js"; | import hoistedNoteService from "./hoisted_note.js"; | ||||||
|  | import treeCache from "./tree_cache.js"; | ||||||
| 
 | 
 | ||||||
| /* | let clipboardBranchIds = []; | ||||||
|  * 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; | let clipboardMode = null; | ||||||
| 
 | 
 | ||||||
| async function pasteAfter(afterNode) { | async function pasteAfter(afterBranchId) { | ||||||
|     if (isClipboardEmpty()) { |     if (isClipboardEmpty()) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (clipboardMode === 'cut') { |     if (clipboardMode === 'cut') { | ||||||
|         const nodes = clipboardNodeKeys.map(nodeKey => treeService.getNodeByKey(nodeKey)); |         await treeChangesService.moveAfterNode(clipboardBranchIds, afterBranchId); | ||||||
| 
 | 
 | ||||||
|         await treeChangesService.moveAfterNode(nodes, afterNode); |         clipboardBranchIds = []; | ||||||
| 
 |  | ||||||
|         clipboardNodeKeys = []; |  | ||||||
|         clipboardMode = null; |         clipboardMode = null; | ||||||
|     } |     } | ||||||
|     else if (clipboardMode === 'copy') { |     else if (clipboardMode === 'copy') { | ||||||
|         for (const nodeKey of clipboardNodeKeys) { |         const clipboardBranches = clipboardBranchIds.map(branchId => treeCache.getBranch(branchId)); | ||||||
|             const clipNode = treeService.getNodeByKey(nodeKey); |  | ||||||
| 
 | 
 | ||||||
|             await cloningService.cloneNoteAfter(clipNode.data.noteId, afterNode.data.branchId); |         for (const clipboardBranch of clipboardBranches) { | ||||||
|  |             const clipboardNote = await clipboardBranch.getNote(); | ||||||
|  | 
 | ||||||
|  |             await cloningService.cloneNoteAfter(clipboardNote.noteId, afterBranchId); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // copy will keep clipboardIds and clipboardMode so it's possible to paste into multiple places
 |         // copy will keep clipboardBranchIds and clipboardMode so it's possible to paste into multiple places
 | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|         toastService.throwError("Unrecognized clipboard mode=" + clipboardMode); |         toastService.throwError("Unrecognized clipboard mode=" + clipboardMode); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function pasteInto(parentNode) { | async function pasteInto(parentNoteId) { | ||||||
|     if (isClipboardEmpty()) { |     if (isClipboardEmpty()) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (clipboardMode === 'cut') { |     if (clipboardMode === 'cut') { | ||||||
|         const nodes = clipboardNodeKeys.map(nodeKey => treeService.getNodeByKey(nodeKey)); |         await treeChangesService.moveToNode(clipboardBranchIds, parentNoteId); | ||||||
| 
 | 
 | ||||||
|         await treeChangesService.moveToNode(nodes, parentNode); |         clipboardBranchIds = []; | ||||||
| 
 |  | ||||||
|         await parentNode.setExpanded(true); |  | ||||||
| 
 |  | ||||||
|         clipboardNodeKeys = []; |  | ||||||
|         clipboardMode = null; |         clipboardMode = null; | ||||||
|     } |     } | ||||||
|     else if (clipboardMode === 'copy') { |     else if (clipboardMode === 'copy') { | ||||||
|         for (const nodeKey of clipboardNodeKeys) { |         const clipboardBranches = clipboardBranchIds.map(branchId => treeCache.getBranch(branchId)); | ||||||
|             const clipNode = treeService.getNodeByKey(nodeKey); |  | ||||||
| 
 | 
 | ||||||
|             await cloningService.cloneNoteTo(clipNode.data.noteId, parentNode.data.noteId); |         for (const clipboardBranch of clipboardBranches) { | ||||||
|  |             const clipboardNote = await clipboardBranch.getNote(); | ||||||
|  | 
 | ||||||
|  |             await cloningService.cloneNoteTo(clipboardNote.noteId, parentNoteId); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         await parentNode.setExpanded(true); |         // copy will keep clipboardBranchIds and clipboardMode so it's possible to paste into multiple places
 | ||||||
| 
 |  | ||||||
|         // copy will keep clipboardIds and clipboardMode so it's possible to paste into multiple places
 |  | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|         toastService.throwError("Unrecognized clipboard mode=" + mode); |         toastService.throwError("Unrecognized clipboard mode=" + mode); | ||||||
| @ -72,19 +62,19 @@ async function pasteInto(parentNode) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function copy(nodes) { | function copy(nodes) { | ||||||
|     clipboardNodeKeys = nodes.map(node => node.key); |     clipboardBranchIds = nodes.map(node => node.data.branchId); | ||||||
|     clipboardMode = 'copy'; |     clipboardMode = 'copy'; | ||||||
| 
 | 
 | ||||||
|     toastService.showMessage("Note(s) have been copied into clipboard."); |     toastService.showMessage("Note(s) have been copied into clipboard."); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function cut(nodes) { | function cut(nodes) { | ||||||
|     clipboardNodeKeys = nodes |     clipboardBranchIds = nodes | ||||||
|         .filter(node => node.data.noteId !== hoistedNoteService.getHoistedNoteNoPromise()) |         .filter(node => node.data.noteId !== hoistedNoteService.getHoistedNoteNoPromise()) | ||||||
|         .filter(node => node.getParent().data.noteType !== 'search') |         .filter(node => node.getParent().data.noteType !== 'search') | ||||||
|         .map(node => node.key); |         .map(node => node.key); | ||||||
| 
 | 
 | ||||||
|     if (clipboardNodeKeys.length > 0) { |     if (clipboardBranchIds.length > 0) { | ||||||
|         clipboardMode = 'cut'; |         clipboardMode = 'cut'; | ||||||
| 
 | 
 | ||||||
|         toastService.showMessage("Note(s) have been cut into clipboard."); |         toastService.showMessage("Note(s) have been cut into clipboard."); | ||||||
| @ -92,9 +82,9 @@ function cut(nodes) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function isClipboardEmpty() { | function isClipboardEmpty() { | ||||||
|     clipboardNodeKeys = clipboardNodeKeys.filter(key => !!treeService.getNodeByKey(key)); |     clipboardBranchIds = clipboardBranchIds.filter(branchId => !!treeCache.getBranch(branchId)); | ||||||
| 
 | 
 | ||||||
|     return clipboardNodeKeys.length === 0; |     return clipboardBranchIds.length === 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|  | |||||||
| @ -4,11 +4,10 @@ const $contextMenuContainer = $("#context-menu-container"); | |||||||
| let dateContextMenuOpenedMs = 0; | let dateContextMenuOpenedMs = 0; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {NoteTreeWidget} treeWidget |  | ||||||
|  * @param event - originating click event (used to get coordinates to display menu at position) |  * @param event - originating click event (used to get coordinates to display menu at position) | ||||||
|  * @param {object} contextMenu - needs to have getContextMenuItems() and selectContextMenuItem(e, cmd) |  * @param {object} contextMenu - needs to have getContextMenuItems() and selectContextMenuItem(e, cmd) | ||||||
|  */ |  */ | ||||||
| async function initContextMenu(treeWidget, event, contextMenu) { | async function initContextMenu(event, contextMenu) { | ||||||
|     event.stopPropagation(); |     event.stopPropagation(); | ||||||
| 
 | 
 | ||||||
|     $contextMenuContainer.empty(); |     $contextMenuContainer.empty(); | ||||||
|  | |||||||
| @ -150,7 +150,7 @@ class TreeContextMenu { | |||||||
|             import("../dialogs/move_to.js").then(d => d.showDialog(nodes)); |             import("../dialogs/move_to.js").then(d => d.showDialog(nodes)); | ||||||
|         } |         } | ||||||
|         else if (cmd === "pasteAfter") { |         else if (cmd === "pasteAfter") { | ||||||
|             clipboard.pasteAfter(this.node); |             clipboard.pasteAfter(this.treeWidget, this.node); | ||||||
|         } |         } | ||||||
|         else if (cmd === "pasteInto") { |         else if (cmd === "pasteInto") { | ||||||
|             clipboard.pasteInto(this.node); |             clipboard.pasteInto(this.node); | ||||||
|  | |||||||
| @ -157,7 +157,7 @@ export default class NoteTreeWidget extends BasicWidget { | |||||||
|         $tree.on('contextmenu', '.fancytree-node', e => { |         $tree.on('contextmenu', '.fancytree-node', e => { | ||||||
|             const node = $.ui.fancytree.getNode(e); |             const node = $.ui.fancytree.getNode(e); | ||||||
| 
 | 
 | ||||||
|             contextMenuWidget.initContextMenu(this, e, new TreeContextMenu(this, node)); |             contextMenuWidget.initContextMenu(e, new TreeContextMenu(this, node)); | ||||||
| 
 | 
 | ||||||
|             return false; // blocks default browser right click menu
 |             return false; // blocks default browser right click menu
 | ||||||
|         }); |         }); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam