note converted to module

This commit is contained in:
azivner 2017-11-04 17:54:27 -04:00
parent adc7d15819
commit b44c685f23
11 changed files with 301 additions and 270 deletions

View File

@ -54,10 +54,10 @@ const contextMenuSetup = {
const parentKey = getParentKey(node); const parentKey = getParentKey(node);
const encryption = getParentEncryption(node); const encryption = getParentEncryption(node);
createNote(node, parentKey, 'after', encryption); noteEditor.createNote(node, parentKey, 'after', encryption);
} }
else if (ui.cmd === "insertChildNote") { else if (ui.cmd === "insertChildNote") {
createNote(node, node.key, 'into'); noteEditor.createNote(node, node.key, 'into');
} }
else if (ui.cmd === "encryptSubTree") { else if (ui.cmd === "encryptSubTree") {
encryptSubTree(node.key); encryptSubTree(node.key);

View File

@ -7,7 +7,7 @@ const noteHistory = (function() {
let historyItems = []; let historyItems = [];
async function showCurrentNoteHistory() { async function showCurrentNoteHistory() {
await showNoteHistoryDialog(glob.currentNote.detail.note_id); await showNoteHistoryDialog(noteEditor.getCurrentNoteId());
} }
async function showNoteHistoryDialog(noteId, noteHistoryId) { async function showNoteHistoryDialog(noteId, noteHistoryId) {

View File

@ -9,7 +9,7 @@ const recentNotes = (function() {
function addRecentNote(noteTreeId, noteContentId) { function addRecentNote(noteTreeId, noteContentId) {
setTimeout(() => { setTimeout(() => {
// we include the note into recent list only if the user stayed on the note at least 5 seconds // we include the note into recent list only if the user stayed on the note at least 5 seconds
if (noteTreeId === glob.currentNote.detail.note_id || noteContentId === glob.currentNote.detail.note_id) { if (noteTreeId === noteEditor.getCurrentNoteId() || noteContentId === noteEditor.getCurrentNoteId()) {
// if it's already there, remove the note // if it's already there, remove the note
list = list.filter(note => note !== noteTreeId); list = list.filter(note => note !== noteTreeId);
@ -35,7 +35,7 @@ const recentNotes = (function() {
selectBoxEl.find('option').remove(); selectBoxEl.find('option').remove();
// remove the current note // remove the current note
const recNotes = list.filter(note => note !== glob.currentNote.detail.note_id); const recNotes = list.filter(note => note !== noteEditor.getCurrentNoteId());
$.each(recNotes, (key, valueNoteId) => { $.each(recNotes, (key, valueNoteId) => {
const noteTitle = getFullName(valueNoteId); const noteTitle = getFullName(valueNoteId);

View File

@ -12,7 +12,7 @@ function handleEncryption(requireEncryption, modal) {
open: () => { open: () => {
if (!modal) { if (!modal) {
// dialog steals focus for itself, which is not what we want for non-modal (viewing) // dialog steals focus for itself, which is not what we want for non-modal (viewing)
getNodeByKey(glob.currentNote.detail.note_id).setFocus(); getNodeByKey(noteEditor.getCurrentNoteId()).setFocus();
} }
} }
}); });
@ -117,8 +117,8 @@ $("#encryption-password-form").submit(() => {
function resetEncryptionSession() { function resetEncryptionSession() {
glob.dataKey = null; glob.dataKey = null;
if (glob.currentNote.detail.encryption > 0) { if (noteEditor.getCurrentNote().detail.encryption > 0) {
loadNoteToEditor(glob.currentNote.detail.note_id); noteEditor.loadNoteToEditor(noteEditor.getCurrentNoteId());
for (const noteId of glob.allNoteIds) { for (const noteId of glob.allNoteIds) {
const note = getNodeByKey(noteId); const note = getNodeByKey(noteId);
@ -240,17 +240,17 @@ function encryptNote(note) {
async function encryptNoteAndSendToServer() { async function encryptNoteAndSendToServer() {
await handleEncryption(true, true); await handleEncryption(true, true);
const note = glob.currentNote; const note = noteEditor.getCurrentNote();
updateNoteFromInputs(note); noteEditor.updateNoteFromInputs(note);
encryptNote(note); encryptNote(note);
await saveNoteToServer(note); await noteEditor.saveNoteToServer(note);
await changeEncryptionOnNoteHistory(note.detail.note_id, true); await changeEncryptionOnNoteHistory(note.detail.note_id, true);
setNoteBackgroundIfEncrypted(note); noteEditor.setNoteBackgroundIfEncrypted(note);
} }
async function changeEncryptionOnNoteHistory(noteId, encrypt) { async function changeEncryptionOnNoteHistory(noteId, encrypt) {
@ -287,17 +287,17 @@ async function changeEncryptionOnNoteHistory(noteId, encrypt) {
async function decryptNoteAndSendToServer() { async function decryptNoteAndSendToServer() {
await handleEncryption(true, true); await handleEncryption(true, true);
const note = glob.currentNote; const note = noteEditor.getCurrentNote();
updateNoteFromInputs(note); noteEditor.updateNoteFromInputs(note);
note.detail.encryption = 0; note.detail.encryption = 0;
await saveNoteToServer(note); await noteEditor.saveNoteToServer(note);
await changeEncryptionOnNoteHistory(note.detail.note_id, false); await changeEncryptionOnNoteHistory(note.detail.note_id, false);
setNoteBackgroundIfEncrypted(note); noteEditor.setNoteBackgroundIfEncrypted(note);
} }
function decryptNoteIfNecessary(note) { function decryptNoteIfNecessary(note) {
@ -327,11 +327,11 @@ async function encryptSubTree(noteId) {
} }
}, },
note => { note => {
if (note.detail.note_id === glob.currentNote.detail.note_id) { if (note.detail.note_id === noteEditor.getCurrentNoteId()) {
loadNoteToEditor(note.detail.note_id); noteEditor.loadNoteToEditor(note.detail.note_id);
} }
else { else {
setTreeBasedOnEncryption(note); noteEditor.setTreeBasedOnEncryption(note);
} }
}); });
@ -354,11 +354,11 @@ async function decryptSubTree(noteId) {
} }
}, },
note => { note => {
if (note.detail.note_id === glob.currentNote.detail.note_id) { if (note.detail.note_id === noteEditor.getCurrentNoteId()) {
loadNoteToEditor(note.detail.note_id); noteEditor.loadNoteToEditor(note.detail.note_id);
} }
else { else {
setTreeBasedOnEncryption(note); noteEditor.setTreeBasedOnEncryption(note);
} }
}); });

View File

@ -24,7 +24,7 @@ $(document).bind('keydown', 'alt+t', () => {
$(window).on('beforeunload', () => { $(window).on('beforeunload', () => {
// this makes sure that when user e.g. reloads the page or navigates away from the page, the note's content is saved // 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 // this sends the request asynchronously and doesn't wait for result
saveNoteIfChanged(); noteEditor.saveNoteIfChanged();
}); });
// Overrides the default autocomplete filter function to search for matched on atleast 1 word in each of the input term's words // Overrides the default autocomplete filter function to search for matched on atleast 1 word in each of the input term's words
@ -62,10 +62,10 @@ $.ui.autocomplete.filter = (array, terms) => {
$(document).tooltip({ $(document).tooltip({
items: ".note-editable a", items: ".note-editable a",
content: function(callback) { content: function(callback) {
const noteId = getNoteIdFromLink($(this).attr("href")); const noteId = link.getNoteIdFromLink($(this).attr("href"));
if (noteId !== null) { if (noteId !== null) {
loadNote(noteId).then(note => callback(note.detail.note_text)); noteEditor.loadNote(noteId).then(note => callback(note.detail.note_text));
} }
}, },
close: function(event, ui) close: function(event, ui)

View File

@ -63,6 +63,7 @@ const link = (function() {
return { return {
getNodeIdFromLabel, getNodeIdFromLabel,
getNoteIdFromLink,
createNoteLink createNoteLink
}; };
})(); })();

View File

@ -1,237 +0,0 @@
const tags = {
1: "<b>",
2: "</b>",
3: "<i>",
4: "</i>",
5: "<u>",
6: "</u>",
9: "<s>",
10: "</s>"
};
let noteChangeDisabled = false;
let isNoteChanged = false;
function noteChanged() {
if (noteChangeDisabled) {
return;
}
isNoteChanged = true;
}
async function saveNoteIfChanged() {
if (!isNoteChanged) {
return;
}
const note = glob.currentNote;
updateNoteFromInputs(note);
encryptNoteIfNecessary(note);
await saveNoteToServer(note);
}
setInterval(saveNoteIfChanged, 5000);
$(document).ready(() => {
$("#note-title").on('input', () => {
noteChanged();
});
$('#note-detail').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 parseHtml(contents, note) {
note.links = [];
note.images = [];
note.detail.note_text = contents;
if (!note.detail.encryption) {
const linkRegexp = /<a[^>]+?href="[^"]*kapp#([A-Za-z0-9]{22})"[^>]*?>[^<]+?<\/a>/g;
let match;
while (match = linkRegexp.exec(contents)) {
console.log("adding link for " + match[1]);
note.links.push({
note_id: note.detail.note_id,
target_note_id: match[1]
});
}
}
}
function updateNoteFromInputs(note) {
let contents = $('#note-detail').summernote('code');
parseHtml(contents, note);
let title = $('#note-title').val();
getNodeByKey(note.detail.note_id).setTitle(title);
note.detail.note_title = title;
}
async function saveNoteToServer(note) {
await $.ajax({
url: baseApiUrl + 'notes/' + note.detail.note_id,
type: 'PUT',
data: JSON.stringify(note),
contentType: "application/json",
error: () => {
error("Error saving the note!");
}
});
isNoteChanged = false;
message("Saved!");
}
glob.currentNote = null;
glob.currentNoteLoadTime = null;
function createNewTopLevelNote() {
let rootNode = glob.tree.fancytree("getRootNode");
createNote(rootNode, "root", "into");
}
let newNoteCreated = false;
async function createNote(node, parentKey, target, encryption) {
// if encryption isn't available (user didn't enter password yet), then note is created as unencrypted
// but this is quite weird since user doesn't see where the note is being created so it shouldn't occur often
if (!encryption || !isEncryptionAvailable()) {
encryption = 0;
}
const newNoteName = "new note";
const newNoteNameEncryptedIfNecessary = encryption > 0 ? encryptString(newNoteName) : newNoteName;
const result = await $.ajax({
url: baseApiUrl + 'notes/' + parentKey + '/children' ,
type: 'POST',
data: JSON.stringify({
note_title: newNoteNameEncryptedIfNecessary,
target: target,
target_note_id: node.key,
encryption: encryption
}),
contentType: "application/json"
});
const newNode = {
title: newNoteName,
key: result.note_id,
note_id: result.note_id,
encryption: encryption,
extraClasses: encryption ? "encrypted" : ""
};
glob.allNoteIds.push(result.note_id);
newNoteCreated = true;
if (target === 'after') {
node.appendSibling(newNode).setActive(true);
}
else {
node.addChildren(newNode).setActive(true);
node.folder = true;
node.renderTitle();
}
message("Created!");
}
function setTreeBasedOnEncryption(note) {
const node = getNodeByKey(note.detail.note_id);
node.toggleClass("encrypted", note.detail.encryption > 0);
}
function setNoteBackgroundIfEncrypted(note) {
if (note.detail.encryption > 0) {
$(".note-editable").addClass("encrypted");
$("#encrypt-button").hide();
$("#decrypt-button").show();
}
else {
$(".note-editable").removeClass("encrypted");
$("#encrypt-button").show();
$("#decrypt-button").hide();
}
setTreeBasedOnEncryption(note);
}
async function loadNoteToEditor(noteId) {
const note = await $.get(baseApiUrl + 'notes/' + noteId);
glob.currentNote = note;
glob.currentNoteLoadTime = Math.floor(new Date().getTime() / 1000);
if (newNoteCreated) {
newNoteCreated = false;
$("#note-title").focus().select();
}
await handleEncryption(note.detail.encryption > 0, false);
$("#note-detail-wrapper").show();
// this may fal if the dialog has not been previously opened
try {
$("#encryption-password-dialog").dialog('close');
}
catch(e) {}
$("#encryption-password").val('');
decryptNoteIfNecessary(note);
$("#note-title").val(note.detail.note_title);
noteChangeDisabled = true;
// Clear contents and remove all stored history. This is to prevent undo from going across notes
$('#note-detail').summernote('reset');
$('#note-detail').summernote('code', note.detail.note_text);
document.location.hash = noteId;
recentNotes.addRecentNote(noteId, note.detail.note_id);
noteChangeDisabled = false;
setNoteBackgroundIfEncrypted(note);
}
async function loadNote(noteId) {
const note = await $.get(baseApiUrl + 'notes/' + noteId);
if (note.detail.encryption > 0 && !isEncryptionAvailable()) {
return;
}
decryptNoteIfNecessary(note);
return note;
}

View File

@ -0,0 +1,267 @@
const noteEditor = (function() {
const noteTitleEl = $("#note-title");
const noteDetailEl = $('#note-detail');
const noteEditableEl = $(".note-editable");
const encryptButton = $("#encrypt-button");
const decryptButton = $("#decrypt-button");
const noteDetailWrapperEl = $("#note-detail-wrapper");
const encryptionPasswordDialogEl = $("#encryption-password-dialog");
const encryptionPasswordEl = $("#encryption-password");
let currentNote = null;
let currentNoteLoadTime = null;
let noteChangeDisabled = false;
let isNoteChanged = false;
function getCurrentNote() {
return currentNote;
}
function getCurrentNoteId() {
return currentNote ? currentNote.detail.note_id : null;
}
function getCurrentNoteLoadTime() {
return currentNoteLoadTime;
}
function noteChanged() {
if (noteChangeDisabled) {
return;
}
isNoteChanged = true;
}
async function saveNoteIfChanged() {
if (!isNoteChanged) {
return;
}
const note = noteEditor.getCurrentNote();
updateNoteFromInputs(note);
encryptNoteIfNecessary(note);
await saveNoteToServer(note);
}
function parseHtml(contents, note) {
note.links = [];
note.images = [];
note.detail.note_text = contents;
if (!note.detail.encryption) {
const linkRegexp = /<a[^>]+?href="[^"]*app#([A-Za-z0-9]{22})"[^>]*?>[^<]+?<\/a>/g;
let match;
while (match = linkRegexp.exec(contents)) {
console.log("adding link for " + match[1]);
note.links.push({
note_id: note.detail.note_id,
target_note_id: match[1]
});
}
}
}
function updateNoteFromInputs(note) {
const contents = noteDetailEl.summernote('code');
parseHtml(contents, note);
const title = noteTitleEl.val();
getNodeByKey(note.detail.note_id).setTitle(title);
note.detail.note_title = title;
}
async function saveNoteToServer(note) {
await $.ajax({
url: baseApiUrl + 'notes/' + note.detail.note_id,
type: 'PUT',
data: JSON.stringify(note),
contentType: "application/json",
error: () => {
error("Error saving the note!");
}
});
isNoteChanged = false;
message("Saved!");
}
currentNote = null;
currentNoteLoadTime = null;
function createNewTopLevelNote() {
let rootNode = glob.tree.fancytree("getRootNode");
createNote(rootNode, "root", "into");
}
let newNoteCreated = false;
async function createNote(node, parentKey, target, encryption) {
// if encryption isn't available (user didn't enter password yet), then note is created as unencrypted
// but this is quite weird since user doesn't see where the note is being created so it shouldn't occur often
if (!encryption || !isEncryptionAvailable()) {
encryption = 0;
}
const newNoteName = "new note";
const newNoteNameEncryptedIfNecessary = encryption > 0 ? encryptString(newNoteName) : newNoteName;
const result = await $.ajax({
url: baseApiUrl + 'notes/' + parentKey + '/children' ,
type: 'POST',
data: JSON.stringify({
note_title: newNoteNameEncryptedIfNecessary,
target: target,
target_note_id: node.key,
encryption: encryption
}),
contentType: "application/json"
});
const newNode = {
title: newNoteName,
key: result.note_id,
note_id: result.note_id,
encryption: encryption,
extraClasses: encryption ? "encrypted" : ""
};
glob.allNoteIds.push(result.note_id);
newNoteCreated = true;
if (target === 'after') {
node.appendSibling(newNode).setActive(true);
}
else {
node.addChildren(newNode).setActive(true);
node.folder = true;
node.renderTitle();
}
message("Created!");
}
function setTreeBasedOnEncryption(note) {
const node = getNodeByKey(note.detail.note_id);
node.toggleClass("encrypted", note.detail.encryption > 0);
}
function setNoteBackgroundIfEncrypted(note) {
if (note.detail.encryption > 0) {
noteEditableEl.addClass("encrypted");
encryptButton.hide();
decryptButton.show();
}
else {
noteEditableEl.removeClass("encrypted");
encryptButton.show();
decryptButton.hide();
}
setTreeBasedOnEncryption(note);
}
async function loadNoteToEditor(noteId) {
const note = await $.get(baseApiUrl + 'notes/' + noteId);
currentNote = note;
currentNoteLoadTime = Math.floor(new Date().getTime() / 1000);
if (newNoteCreated) {
newNoteCreated = false;
noteTitleEl.focus().select();
}
await handleEncryption(note.detail.encryption > 0, false);
noteDetailWrapperEl.show();
// this may fal if the dialog has not been previously opened
try {
encryptionPasswordDialogEl.dialog('close');
}
catch(e) {}
encryptionPasswordEl.val('');
decryptNoteIfNecessary(note);
noteTitleEl.val(note.detail.note_title);
noteChangeDisabled = true;
// Clear contents and remove all stored history. This is to prevent undo from going across notes
noteDetailEl.summernote('reset');
noteDetailEl.summernote('code', note.detail.note_text);
document.location.hash = noteId;
recentNotes.addRecentNote(noteId, note.detail.note_id);
noteChangeDisabled = false;
setNoteBackgroundIfEncrypted(note);
}
async function loadNote(noteId) {
const note = await $.get(baseApiUrl + 'notes/' + noteId);
if (note.detail.encryption > 0 && !isEncryptionAvailable()) {
return;
}
decryptNoteIfNecessary(note);
return note;
}
$(document).ready(() => {
noteTitleEl.on('input', () => {
noteChanged();
});
noteDetailEl.summernote({
airMode: true,
height: 300,
callbacks: {
onChange: noteChanged
}
});
// so that tab jumps from note title (which has tabindex 1)
noteEditableEl.attr("tabindex", 2);
});
setInterval(saveNoteIfChanged, 5000);
return {
saveNoteIfChanged,
updateNoteFromInputs,
saveNoteToServer,
createNewTopLevelNote,
createNote,
setNoteBackgroundIfEncrypted,
setTreeBasedOnEncryption,
loadNoteToEditor,
loadNote,
getCurrentNote,
getCurrentNoteId,
getCurrentNoteLoadTime
};
})();

View File

@ -5,8 +5,8 @@ async function checkStatus() {
contentType: "application/json", contentType: "application/json",
data: JSON.stringify({ data: JSON.stringify({
treeLoadTime: glob.treeLoadTime, treeLoadTime: glob.treeLoadTime,
currentNoteId: glob.currentNote ? glob.currentNote.detail.note_id : null, currentNoteId: noteEditor.getCurrentNoteId(),
currentNoteDateModified: glob.currentNoteLoadTime currentNoteDateModified: noteEditor.getCurrentNoteLoadTime()
}), }),
statusCode: { statusCode: {
401: () => { 401: () => {

View File

@ -3,10 +3,10 @@ const keybindings = {
const parentKey = getParentKey(node); const parentKey = getParentKey(node);
const encryption = getParentEncryption(node); const encryption = getParentEncryption(node);
createNote(node, parentKey, 'after', encryption); noteEditor.createNote(node, parentKey, 'after', encryption);
}, },
"ctrl+insert": node => { "ctrl+insert": node => {
createNote(node, node.key, 'into', node.data.encryption); noteEditor.createNote(node, node.key, 'into', node.data.encryption);
}, },
"del": node => { "del": node => {
deleteNode(node); deleteNode(node);
@ -95,7 +95,7 @@ function initFancyTree(notes, startNoteId) {
activate: (event, data) => { activate: (event, data) => {
const node = data.node.data; const node = data.node.data;
saveNoteIfChanged().then(() => loadNoteToEditor(node.note_id)); noteEditor.saveNoteIfChanged().then(() => noteEditor.loadNoteToEditor(node.note_id));
}, },
expand: (event, data) => { expand: (event, data) => {
setExpandedToServer(data.node.key, true); setExpandedToServer(data.node.key, true);
@ -219,7 +219,7 @@ function collapseTree() {
$(document).bind('keydown', 'alt+c', collapseTree); $(document).bind('keydown', 'alt+c', collapseTree);
function scrollToCurrentNote() { function scrollToCurrentNote() {
const node = getNodeByKey(glob.currentNote.detail.note_id); const node = getNodeByKey(noteEditor.getCurrentNoteId());
if (node) { if (node) {
node.makeVisible({scrollIntoView: true}); node.makeVisible({scrollIntoView: true});

View File

@ -33,7 +33,7 @@
</div> </div>
<div class="hide-toggle" style="grid-area: tree-actions"> <div class="hide-toggle" style="grid-area: tree-actions">
<a onclick="createNewTopLevelNote()" title="Create new top level note" class="icon-action"> <a onclick="noteEditor.createNewTopLevelNote()" title="Create new top level note" class="icon-action">
<img src="images/icons/file-plus.png" alt="Create new top level note"/> <img src="images/icons/file-plus.png" alt="Create new top level note"/>
</a> </a>
@ -270,7 +270,7 @@
<script src="javascripts/context_menu.js"></script> <script src="javascripts/context_menu.js"></script>
<!-- Note detail --> <!-- Note detail -->
<script src="javascripts/note.js"></script> <script src="javascripts/note_editor.js"></script>
<script src="javascripts/encryption.js"></script> <script src="javascripts/encryption.js"></script>
<!-- dialogs --> <!-- dialogs -->