diff --git a/src/public/javascripts/mobile.js b/src/public/javascripts/mobile.js index 1c344373a..68f0e32ec 100644 --- a/src/public/javascripts/mobile.js +++ b/src/public/javascripts/mobile.js @@ -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 }); } diff --git a/src/public/javascripts/services/branches.js b/src/public/javascripts/services/branches.js index d8b404210..12e773134 100644 --- a/src/public/javascripts/services/branches.js +++ b/src/public/javascripts/services/branches.js @@ -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, diff --git a/src/public/javascripts/services/context_menu.js b/src/public/javascripts/services/context_menu.js index 539f89d67..16510027f 100644 --- a/src/public/javascripts/services/context_menu.js +++ b/src/public/javascripts/services/context_menu.js @@ -42,7 +42,7 @@ function initContextMenu(event, contextMenuItems, selectContextMenuItem) { }); if (item.enabled !== undefined && !item.enabled) { - $link.addClass("disabled"); + $item.addClass("disabled"); } if (item.items) { diff --git a/src/public/javascripts/services/frontend_script_api.js b/src/public/javascripts/services/frontend_script_api.js index 7f63ddca3..4c1b7f061 100644 --- a/src/public/javascripts/services/frontend_script_api.js +++ b/src/public/javascripts/services/frontend_script_api.js @@ -236,7 +236,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null) { /** * @method - * @returns {string} returns note path of active note + * @returns {Promise} returns note path of active note */ this.getActiveNotePath = treeService.getActiveNotePath; diff --git a/src/public/javascripts/services/hoisted_note.js b/src/public/javascripts/services/hoisted_note.js index db567239d..b01f9ea67 100644 --- a/src/public/javascripts/services/hoisted_note.js +++ b/src/public/javascripts/services/hoisted_note.js @@ -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 } \ No newline at end of file diff --git a/src/public/javascripts/services/note_detail.js b/src/public/javascripts/services/note_detail.js index f779e1d2a..4a50aa5d4 100644 --- a/src/public/javascripts/services/note_detail.js +++ b/src/public/javascripts/services/note_detail.js @@ -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 = $('', { diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index 6233c070b..89a627c4a 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -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); diff --git a/src/public/javascripts/services/tree_context_menu.js b/src/public/javascripts/services/tree_context_menu.js index 3c1fd601a..aca05ac4d 100644 --- a/src/public/javascripts/services/tree_context_menu.js +++ b/src/public/javascripts/services/tree_context_menu.js @@ -110,7 +110,7 @@ async function getTopLevelItems(event) { items: insertChildNoteEnabled ? getNoteTypeItems("insertChildNote") : null, enabled: insertChildNoteEnabled }, { title: "Delete Delete", cmd: "delete", uiIcon: "trash", - enabled: isNotRoot && parentNote.type !== 'search' }, + enabled: isNotRoot && !isHoisted && parentNote.type !== 'search' }, { title: "----" }, isHoisted ? null : { title: "Hoist note Ctrl-H", cmd: "hoist", uiIcon: "empty" }, !isHoisted || !isNotRoot ? null : { title: "Unhoist note Ctrl-H", 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', { diff --git a/src/public/javascripts/services/tree_keybindings.js b/src/public/javascripts/services/tree_keybindings.js index 16281ee14..647ab306c 100644 --- a/src/public/javascripts/services/tree_keybindings.js +++ b/src/public/javascripts/services/tree_keybindings.js @@ -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); } }, diff --git a/src/public/javascripts/services/tree_utils.js b/src/public/javascripts/services/tree_utils.js index 1f05871ec..bcd903ec6 100644 --- a/src/public/javascripts/services/tree_utils.js +++ b/src/public/javascripts/services/tree_utils.js @@ -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("/"); } diff --git a/src/public/javascripts/services/utils.js b/src/public/javascripts/services/utils.js index d831a9e5c..0bb0a8ec5 100644 --- a/src/public/javascripts/services/utils.js +++ b/src/public/javascripts/services/utils.js @@ -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 $('
').text(str).html(); } @@ -211,8 +203,6 @@ export default { isElectron, isMac, assertArguments, - isTopLevelNode, - isRootNode, escapeHtml, stopWatch, formatValueWithWhitespace, diff --git a/src/services/notes.js b/src/services/notes.js index f299461da..0d5ee7cfa 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -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"); }