mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	don't allow deleting hoisted note + other related fixes, closes #488
This commit is contained in:
		| @@ -86,10 +86,10 @@ $("#note-menu-button").click(async e => { | ||||
|             enabled: isNotRoot && parentNote.type !== 'search' } | ||||
|     ]; | ||||
|  | ||||
|     contextMenuWidget.initContextMenu(e, items, (event, cmd) => { | ||||
|     contextMenuWidget.initContextMenu(e, items, async (event, cmd) => { | ||||
|         if (cmd === "insertNoteAfter") { | ||||
|             const parentNoteId = node.data.parentNoteId; | ||||
|             const isProtected = treeUtils.getParentProtectedStatus(node); | ||||
|             const isProtected = await treeUtils.getParentProtectedStatus(node); | ||||
|  | ||||
|             treeService.createNote(node, parentNoteId, 'after', { isProtected: isProtected }); | ||||
|         } | ||||
|   | ||||
| @@ -3,9 +3,10 @@ import utils from './utils.js'; | ||||
| import server from './server.js'; | ||||
| import infoService from "./info.js"; | ||||
| import treeCache from "./tree_cache.js"; | ||||
| import hoistedNoteService from "./hoisted_note.js"; | ||||
|  | ||||
| async function moveBeforeNode(nodesToMove, beforeNode) { | ||||
|     nodesToMove = filterRootNote(nodesToMove); | ||||
|     nodesToMove = await filterRootNote(nodesToMove); | ||||
|  | ||||
|     if (beforeNode.data.noteId === 'root') { | ||||
|         alert('Cannot move notes before root note.'); | ||||
| @@ -29,9 +30,9 @@ async function moveBeforeNode(nodesToMove, beforeNode) { | ||||
| } | ||||
|  | ||||
| async function moveAfterNode(nodesToMove, afterNode) { | ||||
|     nodesToMove = filterRootNote(nodesToMove); | ||||
|     nodesToMove = await filterRootNote(nodesToMove); | ||||
|  | ||||
|     if (afterNode.data.noteId === 'root') { | ||||
|     if (afterNode.data.noteId === 'root' || afterNode.data.noteId === await hoistedNoteService.getHoistedNoteId()) { | ||||
|         alert('Cannot move notes after root note.'); | ||||
|         return; | ||||
|     } | ||||
| @@ -55,7 +56,7 @@ async function moveAfterNode(nodesToMove, afterNode) { | ||||
| } | ||||
|  | ||||
| async function moveToNode(nodesToMove, toNode) { | ||||
|     nodesToMove = filterRootNote(nodesToMove); | ||||
|     nodesToMove = await filterRootNote(nodesToMove); | ||||
|  | ||||
|     for (const nodeToMove of nodesToMove) { | ||||
|         const resp = await server.put('branches/' + nodeToMove.data.branchId + '/move-to/' + toNode.data.noteId); | ||||
| @@ -76,13 +77,8 @@ async function moveToNode(nodesToMove, toNode) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| function filterRootNote(nodes) { | ||||
|     // some operations are not possible on root notes | ||||
|     return nodes.filter(node => node.data.noteId !== 'root'); | ||||
| } | ||||
|  | ||||
| async function deleteNodes(nodes) { | ||||
|     nodes = filterRootNote(nodes); | ||||
|     nodes = await filterRootNote(nodes); | ||||
|  | ||||
|     if (nodes.length === 0 || !confirm('Are you sure you want to delete select note(s) and all the sub-notes?')) { | ||||
|         return; | ||||
| @@ -92,8 +88,6 @@ async function deleteNodes(nodes) { | ||||
|         await server.remove('branches/' + node.data.branchId); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     // following code assumes that nodes contain only top-most selected nodes - getSelectedNodes has been | ||||
|     // called with stopOnParent=true | ||||
|     let next = nodes[nodes.length - 1].getNextSibling(); | ||||
| @@ -102,7 +96,7 @@ async function deleteNodes(nodes) { | ||||
|         next = nodes[0].getPrevSibling(); | ||||
|     } | ||||
|  | ||||
|     if (!next && !utils.isTopLevelNode(nodes[0])) { | ||||
|     if (!next && !hoistedNoteService.isTopLevelNode(nodes[0])) { | ||||
|         next = nodes[0].getParent(); | ||||
|     } | ||||
|  | ||||
| @@ -129,7 +123,7 @@ async function deleteNodes(nodes) { | ||||
| } | ||||
|  | ||||
| async function moveNodeUpInHierarchy(node) { | ||||
|     if (utils.isRootNode(node) || utils.isTopLevelNode(node)) { | ||||
|     if (await hoistedNoteService.isRootNode(node) || await hoistedNoteService.isTopLevelNode(node)) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
| @@ -140,7 +134,7 @@ async function moveNodeUpInHierarchy(node) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (!utils.isTopLevelNode(node) && node.getParent().getChildren().length <= 1) { | ||||
|     if (!hoistedNoteService.isTopLevelNode(node) && node.getParent().getChildren().length <= 1) { | ||||
|         node.getParent().folder = false; | ||||
|         node.getParent().renderTitle(); | ||||
|     } | ||||
| @@ -202,6 +196,14 @@ async function changeNode(func, node, beforeNoteId = null, afterNoteId = null) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function filterRootNote(nodes) { | ||||
|     const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); | ||||
|  | ||||
|     return nodes.filter(node => | ||||
|         node.data.noteId !== 'root' | ||||
|         && node.data.noteId !== hoistedNoteId); | ||||
| } | ||||
|  | ||||
| export default { | ||||
|     moveBeforeNode, | ||||
|     moveAfterNode, | ||||
|   | ||||
| @@ -42,7 +42,7 @@ function initContextMenu(event, contextMenuItems, selectContextMenuItem) { | ||||
|                     }); | ||||
|  | ||||
|                 if (item.enabled !== undefined && !item.enabled) { | ||||
|                     $link.addClass("disabled"); | ||||
|                     $item.addClass("disabled"); | ||||
|                 } | ||||
|  | ||||
|                 if (item.items) { | ||||
|   | ||||
| @@ -236,7 +236,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null) { | ||||
|  | ||||
|     /** | ||||
|      * @method | ||||
|      * @returns {string} returns note path of active note | ||||
|      * @returns {Promise<string>} returns note path of active note | ||||
|      */ | ||||
|     this.getActiveNotePath = treeService.getActiveNotePath; | ||||
|  | ||||
|   | ||||
| @@ -26,8 +26,20 @@ async function unhoist() { | ||||
|     await setHoistedNoteId('root'); | ||||
| } | ||||
|  | ||||
| async function isTopLevelNode(node) { | ||||
|     return await isRootNode(node.getParent()); | ||||
| } | ||||
|  | ||||
| async function isRootNode(node) { | ||||
|     // even though check for 'root' should not be necessary, we keep it just in case | ||||
|     return node.data.noteId === "root" | ||||
|         || node.data.noteId === await getHoistedNoteId(); | ||||
| } | ||||
|  | ||||
| export default { | ||||
|     getHoistedNoteId, | ||||
|     setHoistedNoteId, | ||||
|     unhoist | ||||
|     unhoist, | ||||
|     isTopLevelNode, | ||||
|     isRootNode | ||||
| } | ||||
| @@ -270,7 +270,7 @@ async function showChildrenOverview() { | ||||
|  | ||||
|     $childrenOverview.empty(); | ||||
|  | ||||
|     const notePath = treeService.getActiveNotePath(); | ||||
|     const notePath = await treeService.getActiveNotePath(); | ||||
|  | ||||
|     for (const childBranch of await note.getChildBranches()) { | ||||
|         const link = $('<a>', { | ||||
|   | ||||
| @@ -39,10 +39,8 @@ function getActiveNode() { | ||||
|     return $tree.fancytree("getActiveNode"); | ||||
| } | ||||
|  | ||||
| function getActiveNotePath() { | ||||
|     const node = getActiveNode(); | ||||
|  | ||||
|     return treeUtils.getNotePath(node); | ||||
| async function getActiveNotePath() { | ||||
|     return getHashValueFromAddress(); | ||||
| } | ||||
|  | ||||
| async function getNodesByBranchId(branchId) { | ||||
| @@ -325,16 +323,16 @@ async function setExpandedToServer(branchId, isExpanded) { | ||||
| function addRecentNote(branchId, notePath) { | ||||
|     setTimeout(async () => { | ||||
|         // we include the note into recent list only if the user stayed on the note at least 5 seconds | ||||
|         if (notePath && notePath === getActiveNotePath()) { | ||||
|         if (notePath && notePath === await getActiveNotePath()) { | ||||
|             await server.post('recent-notes', { branchId, notePath }); | ||||
|         } | ||||
|     }, 1500); | ||||
| } | ||||
|  | ||||
| function setCurrentNotePathToHash(node) { | ||||
| async function setCurrentNotePathToHash(node) { | ||||
|     utils.assertArguments(node); | ||||
|  | ||||
|     const activeNotePath = treeUtils.getNotePath(node); | ||||
|     const activeNotePath = await treeUtils.getNotePath(node); | ||||
|     const currentBranchId = node.data.branchId; | ||||
|  | ||||
|     document.location.hash = activeNotePath; | ||||
| @@ -412,14 +410,14 @@ function initFancyTree(tree) { | ||||
|                 return false; | ||||
|             } | ||||
|         }, | ||||
|         activate: (event, data) => { | ||||
|         activate: async (event, data) => { | ||||
|             const node = data.node; | ||||
|             const noteId = node.data.noteId; | ||||
|  | ||||
|             // click event won't propagate so let's close context menu manually | ||||
|             contextMenuWidget.hideContextMenu(); | ||||
|  | ||||
|             setCurrentNotePathToHash(node); | ||||
|             await setCurrentNotePathToHash(node); | ||||
|  | ||||
|             noteDetailService.switchToNote(noteId); | ||||
|  | ||||
| @@ -721,7 +719,7 @@ messagingService.subscribeToSyncMessages(syncData => { | ||||
| utils.bindShortcut('ctrl+o', async () => { | ||||
|     const node = getActiveNode(); | ||||
|     const parentNoteId = node.data.parentNoteId; | ||||
|     const isProtected = treeUtils.getParentProtectedStatus(node); | ||||
|     const isProtected = await treeUtils.getParentProtectedStatus(node); | ||||
|  | ||||
|     if (node.data.noteId === 'root' || node.data.noteId === await hoistedNoteService.getHoistedNoteId()) { | ||||
|         return; | ||||
| @@ -774,11 +772,11 @@ utils.bindShortcut('ctrl+p', createNoteInto); | ||||
|  | ||||
| utils.bindShortcut('ctrl+.', scrollToActiveNote); | ||||
|  | ||||
| $(window).bind('hashchange', function() { | ||||
| $(window).bind('hashchange', async function() { | ||||
|     if (isNotePathInAddress()) { | ||||
|         const notePath = getHashValueFromAddress(); | ||||
|  | ||||
|         if (notePath !== '-' && getActiveNotePath() !== notePath) { | ||||
|         if (notePath !== '-' && await getActiveNotePath() !== notePath) { | ||||
|             console.debug("Switching to " + notePath + " because of hash change"); | ||||
|  | ||||
|             activateNote(notePath); | ||||
|   | ||||
| @@ -110,7 +110,7 @@ async function getTopLevelItems(event) { | ||||
|             items: insertChildNoteEnabled ? getNoteTypeItems("insertChildNote") : null, | ||||
|             enabled: insertChildNoteEnabled }, | ||||
|         { title: "Delete <kbd>Delete</kbd>", cmd: "delete", uiIcon: "trash", | ||||
|             enabled: isNotRoot && parentNote.type !== 'search' }, | ||||
|             enabled: isNotRoot && !isHoisted && parentNote.type !== 'search' }, | ||||
|         { title: "----" }, | ||||
|         isHoisted ? null : { title: "Hoist note <kbd>Ctrl-H</kbd>", cmd: "hoist", uiIcon: "empty" }, | ||||
|         !isHoisted || !isNotRoot ? null : { title: "Unhoist note <kbd>Ctrl-H</kbd>", cmd: "unhoist", uiIcon: "arrow-up" }, | ||||
| @@ -156,13 +156,13 @@ async function getContextMenuItems(event) { | ||||
|     return items; | ||||
| } | ||||
|  | ||||
| function selectContextMenuItem(event, cmd) { | ||||
| async function selectContextMenuItem(event, cmd) { | ||||
|     // context menu is always triggered on current node | ||||
|     const node = treeService.getActiveNode(); | ||||
|  | ||||
|     if (cmd.startsWith("insertNoteAfter")) { | ||||
|         const parentNoteId = node.data.parentNoteId; | ||||
|         const isProtected = treeUtils.getParentProtectedStatus(node); | ||||
|         const isProtected = await treeUtils.getParentProtectedStatus(node); | ||||
|         const type = cmd.split("_")[1]; | ||||
|  | ||||
|         treeService.createNote(node, parentNoteId, 'after', { | ||||
|   | ||||
| @@ -109,8 +109,8 @@ const keyBindings = { | ||||
|  | ||||
|         return false; | ||||
|     }, | ||||
|     "backspace": node => { | ||||
|         if (!utils.isRootNode(node)) { | ||||
|     "backspace": async node => { | ||||
|         if (!await hoistedNoteService.isRootNode(node)) { | ||||
|             node.getParent().setActive().then(treeService.clearSelectedNodes); | ||||
|         } | ||||
|     }, | ||||
|   | ||||
| @@ -1,10 +1,11 @@ | ||||
| import utils from './utils.js'; | ||||
| import hoistedNoteService from './hoisted_note.js'; | ||||
| import treeCache from "./tree_cache.js"; | ||||
|  | ||||
| const $tree = $("#tree"); | ||||
|  | ||||
| function getParentProtectedStatus(node) { | ||||
|     return utils.isRootNode(node) ? 0 : node.getParent().data.isProtected; | ||||
| async function getParentProtectedStatus(node) { | ||||
|     return await hoistedNoteService.isRootNode(node) ? 0 : node.getParent().data.isProtected; | ||||
| } | ||||
|  | ||||
| function getNodeByKey(key) { | ||||
| @@ -21,10 +22,15 @@ function getNoteIdFromNotePath(notePath) { | ||||
|     return path[path.length - 1]; | ||||
| } | ||||
|  | ||||
| function getNotePath(node) { | ||||
| async function getNotePath(node) { | ||||
|     if (!node) { | ||||
|         console.error("Node is null"); | ||||
|         return ""; | ||||
|     } | ||||
|  | ||||
|     const path = []; | ||||
|  | ||||
|     while (node && !utils.isRootNode(node)) { | ||||
|     while (node && !await hoistedNoteService.isRootNode(node)) { | ||||
|         if (node.data.noteId) { | ||||
|             path.push(node.data.noteId); | ||||
|         } | ||||
| @@ -32,7 +38,7 @@ function getNotePath(node) { | ||||
|         node = node.getParent(); | ||||
|     } | ||||
|  | ||||
|     path.push('root'); | ||||
|     path.push(node.data.noteId); // root or hoisted noteId | ||||
|  | ||||
|     return path.reverse().join("/"); | ||||
| } | ||||
|   | ||||
| @@ -60,14 +60,6 @@ function assertArguments() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| function isTopLevelNode(node) { | ||||
|     return isRootNode(node.getParent()); | ||||
| } | ||||
|  | ||||
| function isRootNode(node) { | ||||
|     return node.data.noteId === "root"; | ||||
| } | ||||
|  | ||||
| function escapeHtml(str) { | ||||
|     return $('<div/>').text(str).html(); | ||||
| } | ||||
| @@ -211,8 +203,6 @@ export default { | ||||
|     isElectron, | ||||
|     isMac, | ||||
|     assertArguments, | ||||
|     isTopLevelNode, | ||||
|     isRootNode, | ||||
|     escapeHtml, | ||||
|     stopWatch, | ||||
|     formatValueWithWhitespace, | ||||
|   | ||||
| @@ -12,6 +12,7 @@ const Link = require('../entities/link'); | ||||
| const NoteRevision = require('../entities/note_revision'); | ||||
| const Branch = require('../entities/branch'); | ||||
| const Attribute = require('../entities/attribute'); | ||||
| const hoistedNoteService = require('../services/hoisted_note'); | ||||
|  | ||||
| async function getNewNotePosition(parentNoteId, noteData) { | ||||
|     let newNotePos = 0; | ||||
| @@ -364,7 +365,10 @@ async function deleteNote(branch) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (branch.branchId === 'root' || branch.noteId === 'root') { | ||||
|     if (branch.branchId === 'root' | ||||
|         || branch.noteId === 'root' | ||||
|         || branch.noteId === await hoistedNoteService.getHoistedNoteId()) { | ||||
|  | ||||
|         throw new Error("Can't delete root branch/note"); | ||||
|     } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user