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