diff --git a/src/public/javascripts/desktop.js b/src/public/javascripts/desktop.js index b4eaee2b4..8b1ead908 100644 --- a/src/public/javascripts/desktop.js +++ b/src/public/javascripts/desktop.js @@ -34,7 +34,7 @@ import keyboardActionService from "./services/keyboard_actions.js"; import splitService from "./services/split.js"; import optionService from "./services/options.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.isMobile = utils.isMobile; @@ -183,7 +183,6 @@ macInit.init(); searchNotesService.init(); // should be in front of treeService since that one manipulates address bar hash -const appContext = new AppContext(); appContext.showWidgets(); entrypoints.registerEntrypoints(); diff --git a/src/public/javascripts/entities/branch.js b/src/public/javascripts/entities/branch.js index b06a9e523..21a4f1487 100644 --- a/src/public/javascripts/entities/branch.js +++ b/src/public/javascripts/entities/branch.js @@ -21,6 +21,11 @@ class Branch { 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 */ isTopLevel() { return this.parentNoteId === 'root'; diff --git a/src/public/javascripts/services/app_context.js b/src/public/javascripts/services/app_context.js index 4b0abb429..5ecc22ec7 100644 --- a/src/public/javascripts/services/app_context.js +++ b/src/public/javascripts/services/app_context.js @@ -3,7 +3,7 @@ import SearchBoxWidget from "../widgets/search_box.js"; import SearchResultsWidget from "../widgets/search_results.js"; import NoteTreeWidget from "../widgets/note_tree.js"; -export default class AppContext { +class AppContext { constructor() { this.widgets = []; } @@ -30,4 +30,8 @@ export default class AppContext { $leftPane.append($widget); } } -} \ No newline at end of file +} + +const appContext = new AppContext(); + +export default appContext; \ No newline at end of file diff --git a/src/public/javascripts/services/branches.js b/src/public/javascripts/services/branches.js index 1e2a2a7d9..c20338321 100644 --- a/src/public/javascripts/services/branches.js +++ b/src/public/javascripts/services/branches.js @@ -8,76 +8,63 @@ import hoistedNoteService from "./hoisted_note.js"; import noteDetailService from "./note_detail.js"; import ws from "./ws.js"; -async function moveBeforeNode(nodesToMove, beforeNode) { - nodesToMove = await filterRootNote(nodesToMove); +async function moveBeforeNode(branchIdsToMove, beforeBranchId) { + branchIdsToMove = await filterRootNote(branchIdsToMove); - if (beforeNode.data.noteId === 'root') { + if (beforeBranchId === 'root') { alert('Cannot move notes before root note.'); return; } - for (const nodeToMove of nodesToMove) { - const resp = await server.put('branches/' + nodeToMove.data.branchId + '/move-before/' + beforeNode.data.branchId); + for (const branchIdToMove of branchIdsToMove) { + const resp = await server.put(`branches/${branchIdToMove}/move-before/${beforeBranchId}`); if (!resp.success) { alert(resp.message); return; } - - await changeNode( - node => node.moveTo(beforeNode, 'before'), - nodeToMove); } } -async function moveAfterNode(nodesToMove, afterNode) { - nodesToMove = await filterRootNote(nodesToMove); +async function moveAfterNode(branchIdsToMove, afterBranchId) { + 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.'); 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) { - const resp = await server.put('branches/' + nodeToMove.data.branchId + '/move-after/' + afterNode.data.branchId); + for (const branchIdToMove of branchIdsToMove) { + const resp = await server.put(`branches/${branchIdToMove}/move-after/${afterBranchId}`); if (!resp.success) { alert(resp.message); return; } - - await changeNode( - node => node.moveTo(afterNode, 'after'), - nodeToMove); } } -async function moveToNode(nodesToMove, toNode) { - nodesToMove = await filterRootNote(nodesToMove); +async function moveToNode(branchIdsToMove, newParentNoteId) { + branchIdsToMove = await filterRootNote(branchIdsToMove); - for (const nodeToMove of nodesToMove) { - if (nodeToMove.data.noteId === await hoistedNoteService.getHoistedNoteId() - || nodeToMove.getParent().data.noteType === 'search') { + for (const branchIdToMove of branchIdsToMove) { + const branchToMove = treeCache.getBranch(branchIdToMove); + + if (branchToMove.noteId === await hoistedNoteService.getHoistedNoteId() + || (await branchToMove.getParentNote()).type === 'search') { 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) { alert(resp.message); 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; } - 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().renderTitle(); } - - await changeNode( - node => node.moveTo(node.getParent(), 'after'), - node); } -async function changeNode(func, node) { - 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) { +async function filterRootNote(branchIds) { const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); - return nodes.filter(node => - node.data.noteId !== 'root' - && node.data.noteId !== hoistedNoteId); + return branchIds.filter(branchId => { + const branch = treeCache.getBranch(branchId); + + return branch.noteId !== 'root' + && branch.noteId !== hoistedNoteId; + }); } function makeToast(id, message) { diff --git a/src/public/javascripts/services/clipboard.js b/src/public/javascripts/services/clipboard.js index 2c4ca52a0..681d534b4 100644 --- a/src/public/javascripts/services/clipboard.js +++ b/src/public/javascripts/services/clipboard.js @@ -1,70 +1,60 @@ -import treeService from "./tree.js"; import treeChangesService from "./branches.js"; import cloningService from "./cloning.js"; import toastService from "./toast.js"; import hoistedNoteService from "./hoisted_note.js"; +import treeCache from "./tree_cache.js"; -/* - * 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 clipboardBranchIds = []; let clipboardMode = null; -async function pasteAfter(afterNode) { +async function pasteAfter(afterBranchId) { if (isClipboardEmpty()) { return; } if (clipboardMode === 'cut') { - const nodes = clipboardNodeKeys.map(nodeKey => treeService.getNodeByKey(nodeKey)); + await treeChangesService.moveAfterNode(clipboardBranchIds, afterBranchId); - await treeChangesService.moveAfterNode(nodes, afterNode); - - clipboardNodeKeys = []; + clipboardBranchIds = []; clipboardMode = null; } else if (clipboardMode === 'copy') { - for (const nodeKey of clipboardNodeKeys) { - const clipNode = treeService.getNodeByKey(nodeKey); + const clipboardBranches = clipboardBranchIds.map(branchId => treeCache.getBranch(branchId)); - 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 { toastService.throwError("Unrecognized clipboard mode=" + clipboardMode); } } -async function pasteInto(parentNode) { +async function pasteInto(parentNoteId) { if (isClipboardEmpty()) { return; } if (clipboardMode === 'cut') { - const nodes = clipboardNodeKeys.map(nodeKey => treeService.getNodeByKey(nodeKey)); + await treeChangesService.moveToNode(clipboardBranchIds, parentNoteId); - await treeChangesService.moveToNode(nodes, parentNode); - - await parentNode.setExpanded(true); - - clipboardNodeKeys = []; + clipboardBranchIds = []; clipboardMode = null; } else if (clipboardMode === 'copy') { - for (const nodeKey of clipboardNodeKeys) { - const clipNode = treeService.getNodeByKey(nodeKey); + const clipboardBranches = clipboardBranchIds.map(branchId => treeCache.getBranch(branchId)); - 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 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 { toastService.throwError("Unrecognized clipboard mode=" + mode); @@ -72,19 +62,19 @@ async function pasteInto(parentNode) { } function copy(nodes) { - clipboardNodeKeys = nodes.map(node => node.key); + clipboardBranchIds = nodes.map(node => node.data.branchId); clipboardMode = 'copy'; toastService.showMessage("Note(s) have been copied into clipboard."); } function cut(nodes) { - clipboardNodeKeys = nodes + clipboardBranchIds = nodes .filter(node => node.data.noteId !== hoistedNoteService.getHoistedNoteNoPromise()) .filter(node => node.getParent().data.noteType !== 'search') .map(node => node.key); - if (clipboardNodeKeys.length > 0) { + if (clipboardBranchIds.length > 0) { clipboardMode = 'cut'; toastService.showMessage("Note(s) have been cut into clipboard."); @@ -92,9 +82,9 @@ function cut(nodes) { } 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 { diff --git a/src/public/javascripts/services/context_menu.js b/src/public/javascripts/services/context_menu.js index 895b968c4..a0baa028e 100644 --- a/src/public/javascripts/services/context_menu.js +++ b/src/public/javascripts/services/context_menu.js @@ -4,11 +4,10 @@ const $contextMenuContainer = $("#context-menu-container"); let dateContextMenuOpenedMs = 0; /** - * @param {NoteTreeWidget} treeWidget * @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) */ -async function initContextMenu(treeWidget, event, contextMenu) { +async function initContextMenu(event, contextMenu) { event.stopPropagation(); $contextMenuContainer.empty(); diff --git a/src/public/javascripts/services/tree_context_menu.js b/src/public/javascripts/services/tree_context_menu.js index 3739621dd..17b65618f 100644 --- a/src/public/javascripts/services/tree_context_menu.js +++ b/src/public/javascripts/services/tree_context_menu.js @@ -150,7 +150,7 @@ class TreeContextMenu { import("../dialogs/move_to.js").then(d => d.showDialog(nodes)); } else if (cmd === "pasteAfter") { - clipboard.pasteAfter(this.node); + clipboard.pasteAfter(this.treeWidget, this.node); } else if (cmd === "pasteInto") { clipboard.pasteInto(this.node); diff --git a/src/public/javascripts/widgets/note_tree.js b/src/public/javascripts/widgets/note_tree.js index 4e53e16cf..8b3259c82 100644 --- a/src/public/javascripts/widgets/note_tree.js +++ b/src/public/javascripts/widgets/note_tree.js @@ -157,7 +157,7 @@ export default class NoteTreeWidget extends BasicWidget { $tree.on('contextmenu', '.fancytree-node', 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 });