refactoring - moving stuff to separate files

This commit is contained in:
azivner 2017-09-09 12:06:15 -04:00
parent 7c623d9a0b
commit aad90f016b
12 changed files with 478 additions and 464 deletions

View file

@ -152,11 +152,21 @@
<link href="stat/style.css" rel="stylesheet">
<script src="stat/js/init.js"></script>
<script src="stat/js/tree.js"></script>
<script src="stat/js/treeutils.js"></script>
<script src="stat/js/draganddrop.js"></script>
<script src="stat/js/contextmenu.js"></script>
<script src="stat/js/note.js"></script>
<script src="stat/js/notecase2html.js"></script>
<script src="stat/js/html2notecase.js"></script>
<script src="stat/js/encryption.js"></script>
<script src="stat/js/recentnotes.js"></script>
<script src="stat/js/addlink.js"></script>
<script src="stat/js/jumptonote.js"></script>
<script src="stat/js/utils.js"></script>
</body>
</html>

96
static/js/addlink.js Normal file
View file

@ -0,0 +1,96 @@
$(document).bind('keydown', 'alt+l', function() {
$("#noteAutocomplete").val('');
$("#linkTitle").val('');
const noteDetail = $('#noteDetail');
noteDetail.summernote('editor.saveRange');
$("#insertLinkDialog").dialog({
modal: true,
width: 500
});
function setDefaultLinkTitle(noteId) {
const note = getNodeByKey(noteId);
if (!note) {
return;
}
let noteTitle = note.title;
if (noteTitle.endsWith(" (clone)")) {
noteTitle = noteTitle.substr(0, noteTitle.length - 8);
}
$("#linkTitle").val(noteTitle);
}
$("#noteAutocomplete").autocomplete({
source: getAutocompleteItems(globalAllNoteIds),
minLength: 0,
change: function () {
const val = $("#noteAutocomplete").val();
const noteId = getNodeIdFromLabel(val);
if (noteId) {
setDefaultLinkTitle(noteId);
}
},
// this is called when user goes through autocomplete list with keyboard
// at this point the item isn't selected yet so we use supplied ui.item to see where the cursor is
focus: function (event, ui) {
const noteId = getNodeIdFromLabel(ui.item.value);
setDefaultLinkTitle(noteId);
}
});
});
$("#insertLinkForm").submit(function() {
let val = $("#noteAutocomplete").val();
const noteId = getNodeIdFromLabel(val);
if (noteId) {
const linkTitle = $("#linkTitle").val();
const noteDetail = $('#noteDetail');
$("#insertLinkDialog").dialog("close");
noteDetail.summernote('editor.restoreRange');
noteDetail.summernote('createLink', {
text: linkTitle,
url: 'app#' + noteId,
isNewWindow: true
});
}
return false;
});
// when click on link popup, in case of internal link, just go the the referenced note instead of default behavior
// of opening the link in new window/tab
$(document).on('click', 'div.popover-content a', function(e) {
const targetUrl = $(e.target).attr("href");
const noteIdMatch = /app#([A-Za-z0-9]{22})/.exec(targetUrl);
if (noteIdMatch !== null) {
const noteId = noteIdMatch[1];
getNodeByKey(noteId).setActive();
e.preventDefault();
}
});
function getNodeIdFromLabel(label) {
const noteIdMatch = / \(([A-Za-z0-9]{22})\)/.exec(label);
if (noteIdMatch !== null) {
return noteIdMatch[1];
}
return null;
}

63
static/js/contextmenu.js Normal file
View file

@ -0,0 +1,63 @@
const contextMenuSetup = {
delegate: "span.fancytree-title",
autoFocus: true,
menu: [
{title: "Insert note here", cmd: "insertNoteHere", uiIcon: "ui-icon-pencil"},
{title: "Insert child note", cmd: "insertChildNote", uiIcon: "ui-icon-pencil"},
{title: "Delete", cmd: "delete", uiIcon: "ui-icon-trash"},
{title: "----"},
{title: "Cut", cmd: "cut", uiIcon: "ui-icon-scissors"},
{title: "Copy / clone", cmd: "copy", uiIcon: "ui-icon-copy"},
{title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"},
{title: "Paste into", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"}
],
beforeOpen: function (event, ui) {
const node = $.ui.fancytree.getNode(ui.target);
// Modify menu entries depending on node status
globalTree.contextmenu("enableEntry", "pasteAfter", globalClipboardNoteId !== null);
globalTree.contextmenu("enableEntry", "pasteInto", globalClipboardNoteId !== null);
// Activate node on right-click
node.setActive();
// Disable tree keyboard handling
ui.menu.prevKeyboard = node.tree.options.keyboard;
node.tree.options.keyboard = false;
},
close: function (event, ui) {},
select: function (event, ui) {
const node = $.ui.fancytree.getNode(ui.target);
if (ui.cmd === "insertNoteHere") {
const parentKey = getParentKey(node);
const encryption = getParentEncryption(node);
createNote(node, parentKey, 'after', encryption);
}
else if (ui.cmd === "insertChildNote") {
createNote(node, node.key, 'into');
}
else if (ui.cmd === "cut") {
globalClipboardNoteId = node.key;
}
else if (ui.cmd === "pasteAfter") {
const subjectNode = getNodeByKey(globalClipboardNoteId);
moveAfterNode(subjectNode, node);
globalClipboardNoteId = null;
}
else if (ui.cmd === "pasteInto") {
const subjectNode = getNodeByKey(globalClipboardNoteId);
moveToNode(subjectNode, node);
globalClipboardNoteId = null;
}
else if (ui.cmd === "delete") {
deleteNode(node);
}
else {
console.log("Unknown command: " + ui.cmd);
}
}
};

62
static/js/draganddrop.js Normal file
View file

@ -0,0 +1,62 @@
const dragAndDropSetup = {
autoExpandMS: 600,
draggable: { // modify default jQuery draggable options
zIndex: 1000,
scroll: false,
containment: "parent",
revert: "invalid"
},
preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
dragStart: function (node, data) {
// This function MUST be defined to enable dragging for the tree.
// Return false to cancel dragging of node.
return true;
},
dragEnter: function (node, data) {
/* data.otherNode may be null for non-fancytree droppables.
* Return false to disallow dropping on node. In this case
* dragOver and dragLeave are not called.
* Return 'over', 'before, or 'after' to force a hitMode.
* Return ['before', 'after'] to restrict available hitModes.
* Any other return value will calc the hitMode from the cursor position.
*/
// Prevent dropping a parent below another parent (only sort
// nodes under the same parent):
// if(node.parent !== data.otherNode.parent){
// return false;
// }
// Don't allow dropping *over* a node (would create a child). Just
// allow changing the order:
// return ["before", "after"];
// Accept everything:
return true;
},
dragExpand: function (node, data) {
// return false to prevent auto-expanding data.node on hover
},
dragOver: function (node, data) {
},
dragLeave: function (node, data) {
},
dragStop: function (node, data) {
},
dragDrop: function (node, data) {
// This function MUST be defined to enable dropping of items on the tree.
// data.hitMode is 'before', 'after', or 'over'.
if (data.hitMode === "before") {
moveBeforeNode(data.otherNode, node);
}
else if (data.hitMode === "after") {
moveAfterNode(data.otherNode, node);
}
else if (data.hitMode === "over") {
moveToNode(data.otherNode, node);
}
else {
throw new Exception("Unknown hitMode=" + data.hitMode);
}
}
};

View file

@ -14,7 +14,7 @@ function handleEncryption(requireEncryption, modal, callback) {
open: function() {
if (!modal) {
// dialog steals focus for itself, which is not what we want for non-modal (viewing)
getNodeByKey(globalNote.detail.note_id).setFocus();
getNodeByKey(globalCurrentNote.detail.note_id).setFocus();
}
}
});
@ -127,8 +127,8 @@ setInterval(function() {
if (globalLastEncryptionOperationDate !== null && new Date().getTime() - globalLastEncryptionOperationDate.getTime() > globalEncryptionKeyTimeToLive) {
globalEncryptionKey = null;
if (globalNote.detail.encryption > 0) {
loadNote(globalNote.detail.note_id);
if (globalCurrentNote.detail.encryption > 0) {
loadNote(globalCurrentNote.detail.note_id);
for (const noteId of globalAllNoteIds) {
const note = getNodeByKey(noteId);
@ -189,7 +189,7 @@ function encryptNote(note) {
function encryptNoteAndSendToServer() {
handleEncryption(true, true, () => {
const note = globalNote;
const note = globalCurrentNote;
updateNoteFromInputs(note);
@ -203,7 +203,7 @@ function encryptNoteAndSendToServer() {
function decryptNoteAndSendToServer() {
handleEncryption(true, true, () => {
const note = globalNote;
const note = globalCurrentNote;
updateNoteFromInputs(note);

View file

@ -1,5 +1,6 @@
$(function() {
$(window).resize(function() {
// dynamically setting height of tree and note content to match window's height
const fancyTree = $('ul.fancytree-container');
if (fancyTree.length) {
@ -15,6 +16,7 @@ $(function() {
$(window).resize();
});
// hot keys are active also inside inputs and content editables
jQuery.hotkeys.options.filterInputAcceptingElements = true;
jQuery.hotkeys.options.filterContentEditable = true;
@ -27,176 +29,11 @@ $(document).bind('keydown', 'alt+h', function() {
$("#noteDetailWrapper").css("width", hidden ? "750px" : "100%");
});
$(document).bind('keydown', 'alt+q', function() {
$("#recentNotesDialog").dialog({
modal: true,
width: 500
});
let recentNotesSelectBox = $('#recentNotesSelectBox');
recentNotesSelectBox.find('option').remove();
// remove the current note
let recNotes = globalRecentNotes.filter(note => note !== globalNote.detail.note_id);
$.each(recNotes, function(key, valueNoteId) {
let noteTitle = getFullName(valueNoteId);
if (!noteTitle) {
return;
}
let option = $("<option></option>")
.attr("value", valueNoteId)
.text(noteTitle);
// select the first one (most recent one) by default
if (key === 0) {
option.attr("selected", "selected");
}
recentNotesSelectBox.append(option);
});
});
function setActiveNoteBasedOnRecentNotes() {
let noteId = $("#recentNotesSelectBox option:selected").val();
getNodeByKey(noteId).setActive();
$("#recentNotesDialog").dialog('close');
}
$('#recentNotesSelectBox').keydown(function(e) {
let key = e.which;
if (key === 13)// the enter key code
{
setActiveNoteBasedOnRecentNotes();
}
});
$('#recentNotesSelectBox').dblclick(function(e) {
setActiveNoteBasedOnRecentNotes();
});
// when click on link popup, in case of internal link, just go the the referenced note instead of default behavior
// of opening the link in new window/tab
$(document).on('click', 'div.popover-content a', function(e) {
const targetUrl = $(e.target).attr("href");
const noteIdMatch = /app#([A-Za-z0-9]{22})/.exec(targetUrl);
if (noteIdMatch !== null) {
const noteId = noteIdMatch[1];
getNodeByKey(noteId).setActive();
e.preventDefault();
}
});
function getNodeIdFromLabel(label) {
const noteIdMatch = / \(([A-Za-z0-9]{22})\)/.exec(label);
if (noteIdMatch !== null) {
return noteIdMatch[1];
}
return null;
}
function getAutocompleteItems() {
const autocompleteItems = [];
for (const noteId of globalAllNoteIds) {
const fullName = getFullName(noteId);
autocompleteItems.push({
value: fullName + " (" + noteId + ")",
label: fullName
});
}
return autocompleteItems;
}
$(document).bind('keydown', 'alt+l', function() {
$("#noteAutocomplete").val('');
$("#linkTitle").val('');
const noteDetail = $('#noteDetail');
noteDetail.summernote('editor.saveRange');
$("#insertLinkDialog").dialog({
modal: true,
width: 500
});
function setDefaultLinkTitle(noteId) {
const note = getNodeByKey(noteId);
if (!note) {
return;
}
let noteTitle = note.title;
if (noteTitle.endsWith(" (clone)")) {
noteTitle = noteTitle.substr(0, noteTitle.length - 8);
}
$("#linkTitle").val(noteTitle);
}
$("#noteAutocomplete").autocomplete({
source: getAutocompleteItems(),
minLength: 0,
change: function () {
const val = $("#noteAutocomplete").val();
const noteId = getNodeIdFromLabel(val);
if (noteId) {
setDefaultLinkTitle(noteId);
}
},
// this is called when user goes through autocomplete list with keyboard
// at this point the item isn't selected yet so we use supplied ui.item to see where the cursor is
focus: function (event, ui) {
const noteId = getNodeIdFromLabel(ui.item.value);
setDefaultLinkTitle(noteId);
}
});
});
$("#insertLinkForm").submit(function() {
let val = $("#noteAutocomplete").val();
const noteId = getNodeIdFromLabel(val);
if (noteId) {
const linkTitle = $("#linkTitle").val();
const noteDetail = $('#noteDetail');
$("#insertLinkDialog").dialog("close");
noteDetail.summernote('editor.restoreRange');
noteDetail.summernote('createLink', {
text: linkTitle,
url: 'app#' + noteId,
isNewWindow: true
});
}
return false;
});
$(document).bind('keydown', 'alt+s', function() {
$("input[name=search]").focus();
});
// hide (toggle) everything except for the note content for distraction free writing
$(document).bind('keydown', 'alt+t', function() {
const date = new Date();
@ -206,34 +43,8 @@ $(document).bind('keydown', 'alt+t', function() {
$('#noteDetail').summernote('insertText', dateString);
});
$(document).bind('keydown', 'alt+j', function() {
$("#jumpToNoteAutocomplete").val('');
$("#jumpToNoteDialog").dialog({
modal: true,
width: 500
});
$("#jumpToNoteAutocomplete").autocomplete({
source: getAutocompleteItems(),
minLength: 0
});
});
$("#jumpToNoteForm").submit(function() {
const val = $("#jumpToNoteAutocomplete").val();
const noteId = getNodeIdFromLabel(val);
if (noteId) {
getNodeByKey(noteId).setActive();
$("#jumpToNoteDialog").dialog('close');
}
return false;
});
$(window).on('beforeunload', function(){
// asynchronously send the request and don't wait for result
// this makes sure that when user e.g. reloads the page or navigates away from the page, the note's content is saved
// this sends the request asynchronously and doesn't wait for result
saveNoteIfChanged();
});

26
static/js/jumptonote.js Normal file
View file

@ -0,0 +1,26 @@
$(document).bind('keydown', 'alt+j', function() {
$("#jumpToNoteAutocomplete").val('');
$("#jumpToNoteDialog").dialog({
modal: true,
width: 500
});
$("#jumpToNoteAutocomplete").autocomplete({
source: getAutocompleteItems(globalAllNoteIds),
minLength: 0
});
});
$("#jumpToNoteForm").submit(function() {
const val = $("#jumpToNoteAutocomplete").val();
const noteId = getNodeIdFromLabel(val);
if (noteId) {
getNodeByKey(noteId).setActive();
$("#jumpToNoteDialog").dialog('close');
}
return false;
});

View file

@ -21,6 +21,43 @@ function noteChanged() {
isNoteChanged = true;
}
function saveNoteIfChanged(callback) {
if (!isNoteChanged) {
if (callback) {
callback();
}
return;
}
const note = globalCurrentNote;
updateNoteFromInputs(note);
encryptNoteIfNecessary(note);
saveNoteToServer(note, callback);
}
setInterval(saveNoteIfChanged, 5000);
$(document).ready(function() {
$("#noteTitle").on('input', function() {
noteChanged();
});
$('#noteDetail').summernote({
airMode: true,
height: 300,
callbacks: {
onChange: noteChanged
}
});
// so that tab jumps from note title (which has tabindex 1)
$(".note-editable").attr("tabindex", 2);
});
function updateNoteFromInputs(note) {
let contents = $('#noteDetail').summernote('code');
@ -54,44 +91,7 @@ function saveNoteToServer(note, callback) {
});
}
function saveNoteIfChanged(callback) {
if (!isNoteChanged) {
if (callback) {
callback();
}
return;
}
const note = globalNote;
updateNoteFromInputs(note);
encryptNoteIfNecessary(note);
saveNoteToServer(note, callback);
}
setInterval(saveNoteIfChanged, 5000);
$(document).ready(function() {
$("#noteTitle").on('input', function() {
noteChanged();
});
$('#noteDetail').summernote({
airMode: true,
height: 300,
callbacks: {
onChange: noteChanged
}
});
// so that tab jumps from note title (which has tabindex 1)
$(".note-editable").attr("tabindex", 2);
});
let globalNote;
let globalCurrentNote;
function createNewTopLevelNote() {
let rootNode = globalTree.fancytree("getRootNode");
@ -149,8 +149,6 @@ function createNote(node, parentKey, target, encryption) {
});
}
globalRecentNotes = [];
function setNoteBackgroundIfEncrypted(note) {
if (note.detail.encryption > 0) {
$(".note-editable").addClass("encrypted");
@ -169,7 +167,7 @@ function setNoteBackgroundIfEncrypted(note) {
function loadNote(noteId) {
$.get(baseUrl + 'notes/' + noteId).then(function(note) {
globalNote = note;
globalCurrentNote = note;
if (newNoteCreated) {
newNoteCreated = false;
@ -213,18 +211,3 @@ function loadNote(noteId) {
});
});
}
function addRecentNote(noteTreeId, noteContentId) {
const origDate = new Date();
setTimeout(function() {
// we include the note into recent list only if the user stayed on the note at least 5 seconds
if (noteTreeId === globalNote.detail.note_id || noteContentId === globalNote.detail.note_id) {
// if it's already there, remove the note
globalRecentNotes = globalRecentNotes.filter(note => note !== noteTreeId);
globalRecentNotes.unshift(noteTreeId);
}
}, 1500);
}

69
static/js/recentnotes.js Normal file
View file

@ -0,0 +1,69 @@
let globalRecentNotes = [];
function addRecentNote(noteTreeId, noteContentId) {
const origDate = new Date();
setTimeout(function() {
// we include the note into recent list only if the user stayed on the note at least 5 seconds
if (noteTreeId === globalCurrentNote.detail.note_id || noteContentId === globalCurrentNote.detail.note_id) {
// if it's already there, remove the note
globalRecentNotes = globalRecentNotes.filter(note => note !== noteTreeId);
globalRecentNotes.unshift(noteTreeId);
}
}, 1500);
}
$(document).bind('keydown', 'alt+q', function() {
$("#recentNotesDialog").dialog({
modal: true,
width: 500
});
let recentNotesSelectBox = $('#recentNotesSelectBox');
recentNotesSelectBox.find('option').remove();
// remove the current note
let recNotes = globalRecentNotes.filter(note => note !== globalCurrentNote.detail.note_id);
$.each(recNotes, function(key, valueNoteId) {
let noteTitle = getFullName(valueNoteId);
if (!noteTitle) {
return;
}
let option = $("<option></option>")
.attr("value", valueNoteId)
.text(noteTitle);
// select the first one (most recent one) by default
if (key === 0) {
option.attr("selected", "selected");
}
recentNotesSelectBox.append(option);
});
});
function setActiveNoteBasedOnRecentNotes() {
let noteId = $("#recentNotesSelectBox option:selected").val();
getNodeByKey(noteId).setActive();
$("#recentNotesDialog").dialog('close');
}
$('#recentNotesSelectBox').keydown(function(e) {
let key = e.which;
if (key === 13)// the enter key code
{
setActiveNoteBasedOnRecentNotes();
}
});
$('#recentNotesSelectBox').dblclick(function(e) {
setActiveNoteBasedOnRecentNotes();
});

View file

@ -25,7 +25,7 @@ function moveToNode(node, toNode) {
url: baseUrl + 'notes/' + node.key + '/moveTo/' + toNode.key,
type: 'PUT',
contentType: "application/json",
success: function (result) {
success: function () {
node.moveTo(toNode);
toNode.setExpanded(true);
@ -66,12 +66,22 @@ function deleteNode(node) {
}
}
function getParentKey(node) {
return (node.getParent() === null || node.getParent().key === "root_1") ? "root" : node.getParent().key;
function moveNodeUp(node) {
if (node.getParent() !== null) {
$.ajax({
url: baseUrl + 'notes/' + node.key + '/moveAfter/' + node.getParent().key,
type: 'PUT',
contentType: "application/json",
success: function () {
if (node.getParent() !== null && node.getParent().getChildren().length <= 1) {
node.getParent().folder = false;
node.getParent().renderTitle();
}
function getParentEncryption(node) {
return node.getParent() === null ? 0 : node.getParent().data.encryption;
node.moveTo(node.getParent(), 'after');
}
});
}
}
const keybindings = {
@ -101,21 +111,7 @@ const keybindings = {
}
},
"shift+left": function(node) {
if (node.getParent() !== null) {
$.ajax({
url: baseUrl + 'notes/' + node.key + '/moveAfter/' + node.getParent().key,
type: 'PUT',
contentType: "application/json",
success: function() {
if (node.getParent() !== null && node.getParent().getChildren().length <= 1) {
node.getParent().folder = false;
node.getParent().renderTitle();
}
node.moveTo(node.getParent(), 'after');
}
});
}
moveNodeUp(node);
},
"shift+right": function(node) {
let toNode = node.getPrevSibling();
@ -132,40 +128,11 @@ const keybindings = {
let globalAllNoteIds = [];
let globalTree;
function getNodeByKey(noteId) {
return globalTree.fancytree('getNodeByKey', noteId);
}
function getFullName(noteId) {
let note = getNodeByKey(noteId);
const path = [];
while (note) {
path.push(note.title);
note = note.getParent();
}
// remove "root" element
path.pop();
return path.reverse().join(" > ");
}
const globalTree = $("#tree");
let globalClipboardNoteId = null;
$(function(){
$.get(baseUrl + 'tree').then(resp => {
const notes = resp.notes;
let startNoteId = resp.start_note_id;
if (document.location.hash) {
startNoteId = document.location.hash.substr(1); // strip initial #
}
function copyTitle(notes) {
function prepareNoteTree(notes) {
for (const note of notes) {
globalAllNoteIds.push(note.note_id);
@ -186,14 +153,12 @@ $(function(){
note.expanded = note.is_expanded;
if (note.children && note.children.length > 0) {
copyTitle(note.children);
prepareNoteTree(note.children);
}
}
}
copyTitle(notes);
function setExpanded(note_id, is_expanded) {
function setExpandedToServer(note_id, is_expanded) {
expanded_num = is_expanded ? 1 : 0;
$.ajax({
@ -204,7 +169,17 @@ $(function(){
});
}
globalTree = $("#tree");
$(function(){
$.get(baseUrl + 'tree').then(resp => {
const notes = resp.notes;
let startNoteId = resp.start_note_id;
if (document.location.hash) {
startNoteId = document.location.hash.substr(1); // strip initial #
}
prepareNoteTree(notes);
globalTree.fancytree({
autoScroll: true,
extensions: ["hotkeys", "filter", "dnd"],
@ -215,10 +190,10 @@ $(function(){
saveNoteIfChanged(() => loadNote(node.note_id));
},
expand: function(event, data) {
setExpanded(data.node.key, true);
setExpandedToServer(data.node.key, true);
},
collapse: function(event, data) {
setExpanded(data.node.key, false);
setExpandedToServer(data.node.key, false);
},
init: function(event, data) {
if (startNoteId) {
@ -242,133 +217,10 @@ $(function(){
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: {
autoExpandMS: 600,
draggable: { // modify default jQuery draggable options
zIndex: 1000,
scroll: false,
containment: "parent",
revert: "invalid"
},
preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
dragStart: function(node, data) {
// This function MUST be defined to enable dragging for the tree.
// Return false to cancel dragging of node.
return true;
},
dragEnter: function(node, data) {
/* data.otherNode may be null for non-fancytree droppables.
* Return false to disallow dropping on node. In this case
* dragOver and dragLeave are not called.
* Return 'over', 'before, or 'after' to force a hitMode.
* Return ['before', 'after'] to restrict available hitModes.
* Any other return value will calc the hitMode from the cursor position.
*/
// Prevent dropping a parent below another parent (only sort
// nodes under the same parent):
// if(node.parent !== data.otherNode.parent){
// return false;
// }
// Don't allow dropping *over* a node (would create a child). Just
// allow changing the order:
// return ["before", "after"];
// Accept everything:
return true;
},
dragExpand: function(node, data) {
// return false to prevent auto-expanding data.node on hover
},
dragOver: function(node, data) {
},
dragLeave: function(node, data) {
},
dragStop: function(node, data) {
},
dragDrop: function(node, data) {
// This function MUST be defined to enable dropping of items on the tree.
// data.hitMode is 'before', 'after', or 'over'.
if (data.hitMode === "before") {
moveBeforeNode(data.otherNode, node);
}
else if (data.hitMode === "after") {
moveAfterNode(data.otherNode, node);
}
else if (data.hitMode === "over") {
moveToNode(data.otherNode, node);
}
else {
throw new Exception("Unknown hitMode=" + data.hitMode);
}
}
}
dnd: dragAndDropSetup
});
globalTree.contextmenu({
delegate: "span.fancytree-title",
autoFocus: true,
menu: [
{title: "Insert note here", cmd: "insertNoteHere", uiIcon: "ui-icon-pencil"},
{title: "Insert child note", cmd: "insertChildNote", uiIcon: "ui-icon-pencil"},
{title: "Delete", cmd: "delete", uiIcon: "ui-icon-trash"},
{title: "----"},
{title: "Cut", cmd: "cut", uiIcon: "ui-icon-scissors"},
{title: "Copy / clone", cmd: "copy", uiIcon: "ui-icon-copy"},
{title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"},
{title: "Paste into", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"}
],
beforeOpen: function (event, ui) {
const node = $.ui.fancytree.getNode(ui.target);
// Modify menu entries depending on node status
globalTree.contextmenu("enableEntry", "pasteAfter", globalClipboardNoteId !== null);
globalTree.contextmenu("enableEntry", "pasteInto", globalClipboardNoteId !== null);
// Activate node on right-click
node.setActive();
// Disable tree keyboard handling
ui.menu.prevKeyboard = node.tree.options.keyboard;
node.tree.options.keyboard = false;
},
close: function (event, ui) {},
select: function (event, ui) {
const node = $.ui.fancytree.getNode(ui.target);
if (ui.cmd === "insertNoteHere") {
const parentKey = getParentKey(node);
const encryption = getParentEncryption(node);
createNote(node, parentKey, 'after', encryption);
}
else if (ui.cmd === "insertChildNote") {
createNote(node, node.key, 'into');
}
else if (ui.cmd === "cut") {
globalClipboardNoteId = node.key;
}
else if (ui.cmd === "pasteAfter") {
const subjectNode = getNodeByKey(globalClipboardNoteId);
moveAfterNode(subjectNode, node);
globalClipboardNoteId = null;
}
else if (ui.cmd === "pasteInto") {
const subjectNode = getNodeByKey(globalClipboardNoteId);
moveToNode(subjectNode, node);
globalClipboardNoteId = null;
}
else if (ui.cmd === "delete") {
deleteNode(node);
}
else {
console.log("Unknown command: " + ui.cmd);
}
}
});
globalTree.contextmenu(contextMenuSetup);
});
});

27
static/js/treeutils.js Normal file
View file

@ -0,0 +1,27 @@
function getParentKey(node) {
return (node.getParent() === null || node.getParent().key === "root_1") ? "root" : node.getParent().key;
}
function getParentEncryption(node) {
return node.getParent() === null ? 0 : node.getParent().data.encryption;
}
function getNodeByKey(noteId) {
return globalTree.fancytree('getNodeByKey', noteId);
}
function getFullName(noteId) {
let note = getNodeByKey(noteId);
const path = [];
while (note) {
path.push(note.title);
note = note.getParent();
}
// remove "root" element
path.pop();
return path.reverse().join(" > ");
}

View file

@ -14,6 +14,21 @@ function error(str) {
error.fadeOut(10000);
}
function getAutocompleteItems(notes) {
const autocompleteItems = [];
for (const noteId of notes) {
const fullName = getFullName(noteId);
autocompleteItems.push({
value: fullName + " (" + noteId + ")",
label: fullName
});
}
return autocompleteItems;
}
function uint8ToBase64(u8Arr) {
const CHUNK_SIZE = 0x8000; //arbitrary number
const length = u8Arr.length;