mirror of
https://github.com/zadam/trilium.git
synced 2024-09-21 16:16:04 +08:00
refactoring - moving stuff to separate files
This commit is contained in:
parent
7c623d9a0b
commit
aad90f016b
|
@ -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
96
static/js/addlink.js
Normal 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
63
static/js/contextmenu.js
Normal 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
62
static/js/draganddrop.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
26
static/js/jumptonote.js
Normal 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;
|
||||
});
|
|
@ -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;
|
||||
|
@ -212,19 +210,4 @@ function loadNote(noteId) {
|
|||
setNoteBackgroundIfEncrypted(note);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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
69
static/js/recentnotes.js
Normal 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();
|
||||
});
|
|
@ -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,30 +128,47 @@ 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 prepareNoteTree(notes) {
|
||||
for (const note of notes) {
|
||||
globalAllNoteIds.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: baseUrl + 'notes/' + note_id + '/expanded/' + expanded_num,
|
||||
type: 'PUT',
|
||||
contentType: "application/json",
|
||||
success: function(result) {}
|
||||
});
|
||||
}
|
||||
|
||||
$(function(){
|
||||
$.get(baseUrl + 'tree').then(resp => {
|
||||
const notes = resp.notes;
|
||||
|
@ -165,46 +178,8 @@ $(function(){
|
|||
startNoteId = document.location.hash.substr(1); // strip initial #
|
||||
}
|
||||
|
||||
function copyTitle(notes) {
|
||||
for (const note of notes) {
|
||||
globalAllNoteIds.push(note.note_id);
|
||||
prepareNoteTree(notes);
|
||||
|
||||
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) {
|
||||
copyTitle(note.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
copyTitle(notes);
|
||||
|
||||
function setExpanded(note_id, is_expanded) {
|
||||
expanded_num = is_expanded ? 1 : 0;
|
||||
|
||||
$.ajax({
|
||||
url: baseUrl + 'notes/' + note_id + '/expanded/' + expanded_num,
|
||||
type: 'PUT',
|
||||
contentType: "application/json",
|
||||
success: function(result) {}
|
||||
});
|
||||
}
|
||||
|
||||
globalTree = $("#tree");
|
||||
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
27
static/js/treeutils.js
Normal 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(" > ");
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue