From 9880a4d6f6f292a706701e768ca7c7740f1244ea Mon Sep 17 00:00:00 2001 From: azivner Date: Sat, 4 Nov 2017 19:28:49 -0400 Subject: [PATCH] tree converted to module --- public/javascripts/context_menu.js | 6 +- public/javascripts/note_editor.js | 3 +- public/javascripts/note_tree.js | 290 +++++++++++++++++++++++++++++ public/javascripts/status.js | 8 +- public/javascripts/tree.js | 275 --------------------------- public/javascripts/tree_utils.js | 4 +- views/index.ejs | 8 +- 7 files changed, 308 insertions(+), 286 deletions(-) create mode 100644 public/javascripts/note_tree.js delete mode 100644 public/javascripts/tree.js diff --git a/public/javascripts/context_menu.js b/public/javascripts/context_menu.js index df3d673d0..a62505a94 100644 --- a/public/javascripts/context_menu.js +++ b/public/javascripts/context_menu.js @@ -1,3 +1,5 @@ +const treeEl = $("#tree"); + function pasteAfter(node) { const subjectNode = getNodeByKey(glob.clipboardNoteId); @@ -37,8 +39,8 @@ const contextMenuSetup = { beforeOpen: (event, ui) => { const node = $.ui.fancytree.getNode(ui.target); // Modify menu entries depending on node status - glob.tree.contextmenu("enableEntry", "pasteAfter", glob.clipboardNoteId !== null); - glob.tree.contextmenu("enableEntry", "pasteInto", glob.clipboardNoteId !== null); + treeEl.contextmenu("enableEntry", "pasteAfter", glob.clipboardNoteId !== null); + treeEl.contextmenu("enableEntry", "pasteInto", glob.clipboardNoteId !== null); // Activate node on right-click node.setActive(); diff --git a/public/javascripts/note_editor.js b/public/javascripts/note_editor.js index b1231fe6d..f39a26fa1 100644 --- a/public/javascripts/note_editor.js +++ b/public/javascripts/note_editor.js @@ -1,4 +1,5 @@ const noteEditor = (function() { + const treeEl = $("#tree"); const noteTitleEl = $("#note-title"); const noteDetailEl = $('#note-detail'); const encryptButton = $("#encrypt-button"); @@ -101,7 +102,7 @@ const noteEditor = (function() { currentNoteLoadTime = null; function createNewTopLevelNote() { - let rootNode = glob.tree.fancytree("getRootNode"); + let rootNode = treeEl.fancytree("getRootNode"); createNote(rootNode, "root", "into"); } diff --git a/public/javascripts/note_tree.js b/public/javascripts/note_tree.js new file mode 100644 index 000000000..55acb908a --- /dev/null +++ b/public/javascripts/note_tree.js @@ -0,0 +1,290 @@ +const noteTree = (function() { + const noteDetailEl = $('#note-detail'); + const treeEl = $("#tree"); + let treeLoadTime = null; + + glob.allNoteIds = []; + glob.clipboardNoteId = null; + + function getTreeLoadTime() { + return treeLoadTime; + } + + function prepareNoteTree(notes) { + for (const note of notes) { + glob.allNoteIds.push(note.note_id); + + if (note.encryption > 0) { + note.title = "[encrypted]"; + + note.extraClasses = "encrypted"; + } + else { + note.title = note.note_title; + + if (note.is_clone) { + note.title += " (clone)"; + } + } + + note.key = note.note_id; + note.expanded = note.is_expanded; + + if (note.children && note.children.length > 0) { + prepareNoteTree(note.children); + } + } + } + + function setExpandedToServer(note_id, is_expanded) { + expanded_num = is_expanded ? 1 : 0; + + $.ajax({ + url: baseApiUrl + 'notes/' + note_id + '/expanded/' + expanded_num, + type: 'PUT', + contentType: "application/json", + success: result => {} + }); + } + + function initFancyTree(notes, startNoteId) { + const keybindings = { + "insert": node => { + const parentKey = getParentKey(node); + const encryption = getParentEncryption(node); + + noteEditor.createNote(node, parentKey, 'after', encryption); + }, + "ctrl+insert": node => { + noteEditor.createNote(node, node.key, 'into', node.data.encryption); + }, + "del": node => { + deleteNode(node); + }, + "shift+up": node => { + const beforeNode = node.getPrevSibling(); + + if (beforeNode !== null) { + moveBeforeNode(node, beforeNode); + } + }, + "shift+down": node => { + let afterNode = node.getNextSibling(); + if (afterNode !== null) { + moveAfterNode(node, afterNode); + } + }, + "shift+left": node => { + moveNodeUp(node); + }, + "shift+right": node => { + let toNode = node.getPrevSibling(); + + if (toNode !== null) { + moveToNode(node, toNode); + } + }, + "return": node => { + // doesn't work :-/ + noteDetailEl.summernote('focus'); + } + }; + + treeEl.fancytree({ + autoScroll: true, + extensions: ["hotkeys", "filter", "dnd"], + source: notes, + scrollParent: $("#tree"), + activate: (event, data) => { + const node = data.node.data; + + noteEditor.saveNoteIfChanged().then(() => noteEditor.loadNoteToEditor(node.note_id)); + }, + expand: (event, data) => { + setExpandedToServer(data.node.key, true); + }, + collapse: (event, data) => { + setExpandedToServer(data.node.key, false); + }, + init: (event, data) => { + if (startNoteId) { + data.tree.activateKey(startNoteId); + } + + $(window).resize(); + }, + hotkeys: { + keydown: keybindings + }, + filter: { + autoApply: true, // Re-apply last filter if lazy data is loaded + autoExpand: true, // Expand all branches that contain matches while filtered + counter: false, // Show a badge with number of matching child nodes near parent icons + fuzzy: false, // Match single characters in order, e.g. 'fb' will match 'FooBar' + hideExpandedCounter: true, // Hide counter badge if parent is expanded + hideExpanders: false, // Hide expanders if all child nodes are hidden by filter + highlight: true, // Highlight matches by wrapping inside tags + leavesOnly: false, // Match end nodes only + nodata: true, // Display a 'no data' status node if result is empty + mode: "hide" // Grayout unmatched nodes (pass "hide" to remove unmatched node instead) + }, + dnd: dragAndDropSetup, + keydown: (event, data) => { + const node = data.node; + // Eat keyboard events, when a menu is open + if ($(".contextMenu:visible").length > 0) + return false; + + switch (event.which) { + // Open context menu on [Space] key (simulate right click) + case 32: // [Space] + $(node.span).trigger("mousedown", { + preventDefault: true, + button: 2 + }) + .trigger("mouseup", { + preventDefault: true, + pageX: node.span.offsetLeft, + pageY: node.span.offsetTop, + button: 2 + }); + return false; + + // Handle Ctrl-C, -X and -V + // case 67: + // if (event.ctrlKey) { // Ctrl-C + // copyPaste("copy", node); + // return false; + // } + // break; + case 86: + console.log("CTRL-V"); + + if (event.ctrlKey) { // Ctrl-V + pasteAfter(node); + return false; + } + break; + case 88: + console.log("CTRL-X"); + + if (event.ctrlKey) { // Ctrl-X + cut(node); + return false; + } + break; + } + } + }); + + treeEl.contextmenu(contextMenuSetup); + } + + function loadTree() { + return $.get(baseApiUrl + 'tree').then(resp => { + const notes = resp.notes; + let startNoteId = resp.start_note_id; + encryption.setEncryptionSalt(resp.password_derived_key_salt); + encryption.setEncryptionSessionTimeout(resp.encryption_session_timeout); + encryption.setEncryptedDataKey(resp.encrypted_data_key); + treeLoadTime = resp.tree_load_time; + + // add browser ID header to all AJAX requests + $.ajaxSetup({ + headers: { 'x-browser-id': resp.browser_id } + }); + + if (document.location.hash) { + startNoteId = document.location.hash.substr(1); // strip initial # + } + + prepareNoteTree(notes); + + return { + notes: notes, + startNoteId: startNoteId + }; + }); + } + + $(() => { + loadTree().then(resp => { + initFancyTree(resp.notes, resp.startNoteId); + }); + }); + + function collapseTree() { + treeEl.fancytree("getRootNode").visit(node => { + node.setExpanded(false); + }); + } + + $(document).bind('keydown', 'alt+c', collapseTree); + + function scrollToCurrentNote() { + const node = getNodeByKey(noteEditor.getCurrentNoteId()); + + if (node) { + node.makeVisible({scrollIntoView: true}); + + node.setFocus(); + } + } + + function showSearch() { + $("#search-box").show(); + + $("input[name=search]").focus(); + } + + function toggleSearch() { + if ($("#search-box:hidden").length) { + showSearch(); + } + else { + resetSearch(); + + $("#search-box").hide(); + } + } + + function resetSearch() { + $("input[name=search]").val(""); + + const tree = treeEl.fancytree("getTree"); + tree.clearFilter(); + } + + $("button#reset-search-button").click(resetSearch); + + $("input[name=search]").keyup(e => { + const searchString = $("input[name=search]").val(); + + if (e && e.which === $.ui.keyCode.ESCAPE || $.trim(searchString) === "") { + $("button#reset-search-button").click(); + return; + } + + if (e && e.which === $.ui.keyCode.ENTER) { + $.get(baseApiUrl + 'notes?search=' + searchString).then(resp => { + console.log("search: ", resp); + + // Pass a string to perform case insensitive matching + const tree = treeEl.fancytree("getTree"); + tree.filterBranches(node => { + return resp.includes(node.data.note_id); + }); + }); + } + }).focus(); + + $(document).bind('keydown', 'alt+s', showSearch); + + return { + getTreeLoadTime, + loadTree, + collapseTree, + scrollToCurrentNote, + toggleSearch + }; +})(); \ No newline at end of file diff --git a/public/javascripts/status.js b/public/javascripts/status.js index 2c3a0697d..fd8e02edc 100644 --- a/public/javascripts/status.js +++ b/public/javascripts/status.js @@ -1,10 +1,12 @@ +const treeEl = $("#tree"); + async function checkStatus() { const resp = await $.ajax({ url: baseApiUrl + 'status', type: 'POST', contentType: "application/json", data: JSON.stringify({ - treeLoadTime: glob.treeLoadTime, + treeLoadTime: noteTree.getTreeLoadTime(), currentNoteId: noteEditor.getCurrentNoteId(), currentNoteDateModified: noteEditor.getCurrentNoteLoadTime() }), @@ -22,12 +24,12 @@ async function checkStatus() { }); if (resp.changedTree) { - const treeResp = await loadTree(); + const treeResp = await noteTree.loadTree(); console.log("Reloading tree because of background changes"); // this will also reload the note content - await glob.tree.fancytree('getTree').reload(treeResp.notes); + await treeEl.fancytree('getTree').reload(treeResp.notes); encryption.decryptTreeItems(); } diff --git a/public/javascripts/tree.js b/public/javascripts/tree.js deleted file mode 100644 index cc9dbe9ce..000000000 --- a/public/javascripts/tree.js +++ /dev/null @@ -1,275 +0,0 @@ -const keybindings = { - "insert": node => { - const parentKey = getParentKey(node); - const encryption = getParentEncryption(node); - - noteEditor.createNote(node, parentKey, 'after', encryption); - }, - "ctrl+insert": node => { - noteEditor.createNote(node, node.key, 'into', node.data.encryption); - }, - "del": node => { - deleteNode(node); - }, - "shift+up": node => { - const beforeNode = node.getPrevSibling(); - - if (beforeNode !== null) { - moveBeforeNode(node, beforeNode); - } - }, - "shift+down": node => { - let afterNode = node.getNextSibling(); - if (afterNode !== null) { - moveAfterNode(node, afterNode); - } - }, - "shift+left": node => { - moveNodeUp(node); - }, - "shift+right": node => { - let toNode = node.getPrevSibling(); - - if (toNode !== null) { - moveToNode(node, toNode); - } - }, - "return": node => { - // doesn't work :-/ - $('#note-detail').summernote('focus'); - } -}; - -glob.allNoteIds = []; -glob.tree = $("#tree"); -glob.clipboardNoteId = null; - -function prepareNoteTree(notes) { - for (const note of notes) { - glob.allNoteIds.push(note.note_id); - - if (note.encryption > 0) { - note.title = "[encrypted]"; - - note.extraClasses = "encrypted"; - } - else { - note.title = note.note_title; - - if (note.is_clone) { - note.title += " (clone)"; - } - } - - note.key = note.note_id; - note.expanded = note.is_expanded; - - if (note.children && note.children.length > 0) { - prepareNoteTree(note.children); - } - } -} - -function setExpandedToServer(note_id, is_expanded) { - expanded_num = is_expanded ? 1 : 0; - - $.ajax({ - url: baseApiUrl + 'notes/' + note_id + '/expanded/' + expanded_num, - type: 'PUT', - contentType: "application/json", - success: result => {} - }); -} - -glob.treeLoadTime; - -function initFancyTree(notes, startNoteId) { - glob.tree.fancytree({ - autoScroll: true, - extensions: ["hotkeys", "filter", "dnd"], - source: notes, - scrollParent: $("#tree"), - activate: (event, data) => { - const node = data.node.data; - - noteEditor.saveNoteIfChanged().then(() => noteEditor.loadNoteToEditor(node.note_id)); - }, - expand: (event, data) => { - setExpandedToServer(data.node.key, true); - }, - collapse: (event, data) => { - setExpandedToServer(data.node.key, false); - }, - init: (event, data) => { - if (startNoteId) { - data.tree.activateKey(startNoteId); - } - - $(window).resize(); - }, - hotkeys: { - keydown: keybindings - }, - filter: { - autoApply: true, // Re-apply last filter if lazy data is loaded - autoExpand: true, // Expand all branches that contain matches while filtered - counter: false, // Show a badge with number of matching child nodes near parent icons - fuzzy: false, // Match single characters in order, e.g. 'fb' will match 'FooBar' - hideExpandedCounter: true, // Hide counter badge if parent is expanded - hideExpanders: false, // Hide expanders if all child nodes are hidden by filter - highlight: true, // Highlight matches by wrapping inside tags - leavesOnly: false, // Match end nodes only - nodata: true, // Display a 'no data' status node if result is empty - mode: "hide" // Grayout unmatched nodes (pass "hide" to remove unmatched node instead) - }, - dnd: dragAndDropSetup, - keydown: (event, data) => { - const node = data.node; - // Eat keyboard events, when a menu is open - if ($(".contextMenu:visible").length > 0) - return false; - - switch (event.which) { - // Open context menu on [Space] key (simulate right click) - case 32: // [Space] - $(node.span).trigger("mousedown", { - preventDefault: true, - button: 2 - }) - .trigger("mouseup", { - preventDefault: true, - pageX: node.span.offsetLeft, - pageY: node.span.offsetTop, - button: 2 - }); - return false; - - // Handle Ctrl-C, -X and -V - // case 67: - // if (event.ctrlKey) { // Ctrl-C - // copyPaste("copy", node); - // return false; - // } - // break; - case 86: - console.log("CTRL-V"); - - if (event.ctrlKey) { // Ctrl-V - pasteAfter(node); - return false; - } - break; - case 88: - console.log("CTRL-X"); - - if (event.ctrlKey) { // Ctrl-X - cut(node); - return false; - } - break; - } - } - }); - - glob.tree.contextmenu(contextMenuSetup); -} - -function loadTree() { - return $.get(baseApiUrl + 'tree').then(resp => { - const notes = resp.notes; - let startNoteId = resp.start_note_id; - encryption.setEncryptionSalt(resp.password_derived_key_salt); - encryption.setEncryptionSessionTimeout(resp.encryption_session_timeout); - encryption.setEncryptedDataKey(resp.encrypted_data_key); - glob.treeLoadTime = resp.tree_load_time; - - // add browser ID header to all AJAX requests - $.ajaxSetup({ - headers: { 'x-browser-id': resp.browser_id } - }); - - if (document.location.hash) { - startNoteId = document.location.hash.substr(1); // strip initial # - } - - prepareNoteTree(notes); - - return { - notes: notes, - startNoteId: startNoteId - }; - }); -} - -$(() => { - loadTree().then(resp => { - initFancyTree(resp.notes, resp.startNoteId); - }); -}); - -function collapseTree() { - glob.tree.fancytree("getRootNode").visit(node => { - node.setExpanded(false); - }); -} - -$(document).bind('keydown', 'alt+c', collapseTree); - -function scrollToCurrentNote() { - const node = getNodeByKey(noteEditor.getCurrentNoteId()); - - if (node) { - node.makeVisible({scrollIntoView: true}); - - node.setFocus(); - } -} - -function showSearch() { - $("#search-box").show(); - - $("input[name=search]").focus(); -} - -$(document).bind('keydown', 'alt+s', showSearch); - -function toggleSearch() { - if ($("#search-box:hidden").length) { - showSearch(); - } - else { - resetSearch(); - - $("#search-box").hide(); - } -} - -function resetSearch() { - $("input[name=search]").val(""); - - const tree = glob.tree.fancytree("getTree"); - tree.clearFilter(); -} - -$("button#reset-search-button").click(resetSearch); - -$("input[name=search]").keyup(e => { - const searchString = $("input[name=search]").val(); - - if (e && e.which === $.ui.keyCode.ESCAPE || $.trim(searchString) === "") { - $("button#reset-search-button").click(); - return; - } - - if (e && e.which === $.ui.keyCode.ENTER) { - $.get(baseApiUrl + 'notes?search=' + searchString).then(resp => { - console.log("search: ", resp); - - // Pass a string to perform case insensitive matching - const tree = glob.tree.fancytree("getTree"); - tree.filterBranches(node => { - return resp.includes(node.data.note_id); - }); - }); - } -}).focus(); \ No newline at end of file diff --git a/public/javascripts/tree_utils.js b/public/javascripts/tree_utils.js index 95da0f9a2..69d0d27f9 100644 --- a/public/javascripts/tree_utils.js +++ b/public/javascripts/tree_utils.js @@ -1,3 +1,5 @@ +const treeEl = $("#tree"); + function getParentKey(node) { return (node.getParent() === null || node.getParent().key === "root_1") ? "root" : node.getParent().key; } @@ -7,7 +9,7 @@ function getParentEncryption(node) { } function getNodeByKey(noteId) { - return glob.tree.fancytree('getNodeByKey', noteId); + return treeEl.fancytree('getNodeByKey', noteId); } function getNoteTitle(noteId) { diff --git a/views/index.ejs b/views/index.ejs index 68333dad9..b2a70f817 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -37,15 +37,15 @@ Create new top level note - + Collapse tree - + Collapse tree - + Search in notes @@ -263,7 +263,7 @@ - +