Refactor note code: close #3, fix #5, fix #6

master
Skylar Ittner il y a 5 ans
Parent 73118e30de
révision 4201def4ee

@ -86,6 +86,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
.notecard[data-favorite="1"][data-fg="FFFFFF"] {
background-image: url(../img/starbg_dark.svg);
}
.notecard[data-favorite="1"][data-fg="000000"] {
background-image: url(../img/starbg_light.svg);
}

@ -28,11 +28,9 @@
<script src="node_modules/marked/marked.min.js"></script>
<script src="node_modules/shufflejs/dist/shuffle.min.js"></script>
<script src="js/delta.js"></script>
<script src="js/NotePostAPI.class.js"></script>
<script src="js/Note.class.js"></script>
<script src="js/Notes.class.js"></script>
<script src="js/NotePostNotes.class.js"></script>
<script src="js/notes.js"></script>
<script src="js/home.js"></script>
<script src="routes.js"></script>

@ -0,0 +1,304 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class Note {
constructor(content, color, noteid) {
if (typeof content != 'string') {
content = "";
}
if (typeof color != 'string') {
color = "FFF59D";
}
if (typeof noteid != 'number') {
noteid = null;
}
this.noteid = noteid;
this.color = color;
this.content = content;
this.title = "";
this.modified = null;
this.favorite = false;
this.status = "NONE";
}
static loadNote(noteid) {
var notes = JSON.parse(localStorage.getItem("notes"));
if (notes == null) {
console.log("localStorage.notes is null");
return null;
}
for (var n in notes) {
if (notes[n].noteid == noteid) {
var note = new Note(notes[n].content, notes[n].color, notes[n].noteid);
note.setModified(notes[n].modified);
note.setFavorite(notes[n].favorite == true);
note.setSyncStatus(notes[n].syncstatus);
note.setTitle();
return note;
}
}
return null;
}
saveNote() {
var noteid = this.getID();
console.log("Saving note: ", this);
var syncstatus = this.getSyncStatus();
if (noteid == null) {
noteid = Math.floor(Math.random() * (9999999999 - 1000000000) + 1000000000);
syncstatus = "LOCAL_ONLY";
}
var jsonobj = {
noteid: noteid,
color: this.getColor(),
content: this.getText(),
modified: this.getModified(),
favorite: this.getFavorite(),
syncstatus: syncstatus
};
var notes = JSON.parse(localStorage.getItem("notes"));
if (notes == null) {
console.log("localStorage.notes is null, using empty array");
notes = [];
}
for (var n in notes) {
if (notes[n].noteid == noteid) {
if (notes[n].syncstatus == "LOCAL_DELETED") {
// If this note was previously saved as deleted, don't save again
return;
}
notes[n] = jsonobj;
console.log(notes);
localStorage.setItem("notes", JSON.stringify(notes));
return;
}
}
notes.push(jsonobj);
console.log(notes);
localStorage.setItem("notes", JSON.stringify(notes));
}
/**
* Save the note to NotePost.
*/
saveToNotePost(success, error, add) {
var self = this;
var data = {
text: this.getText(),
color: this.getColor(),
modified: this.getModified(),
favorite: this.getFavorite() ? "1" : "0",
};
if (typeof add != 'boolean' || add != true) {
data.id = this.getID();
}
console.log("uploading: ", data);
return APICLIENT.post("savenote", data, function (val) {
if (val.status == "OK") {
self.noteid = val.note.noteid;
self.setText(val.note.content);
self.setColor(val.note.color);
self.setModified(val.note.modified);
self.setFavorite(val.note.favorite);
self.setSyncStatus("SYNCED");
if (typeof success == 'function') {
success(self);
}
} else {
if (typeof error == 'function') {
error(val.msg);
}
}
}, function () {
if (typeof error == 'function') {
error();
}
});
}
/**
* Delete this note on the server.
*/
deleteOnNotePost(success, error) {
var self = this;
return APICLIENT.post("deletenote", {id: this.getID()}, function (val) {
if (val.status == "OK") {
if (typeof success == 'function') {
success(self);
}
} else {
if (typeof error == 'function') {
error(val.msg);
}
}
}, function () {
if (typeof error == 'function') {
error();
}
});
}
deleteme() {
this.setSyncStatus("LOCAL_DELETED");
this.saveNote();
}
getSyncStatus() {
return this.status;
}
getID() {
return this.noteid;
}
getText() {
return this.content;
}
getHTML() {
return marked(this.content);
}
getColor() {
this.setColor(this.color);
return this.color;
}
getTitle() {
this.setTitle(this.title);
return this.title;
}
getModified() {
return this.modified;
}
getFavorite() {
return this.favorite == true;
}
getTextColor() {
var color = this.getColor();
var r = parseInt(color.substring(0, 2), 16);
var g = parseInt(color.substring(2, 4), 16);
var b = parseInt(color.substring(4, 6), 16);
var contrast = Math.sqrt(r * r * 0.241 + g * g * 0.691 + b * b * 0.068);
if (contrast > 130) {
return "000000";
} else {
return "FFFFFF";
}
}
setSyncStatus(status) {
this.status = status;
}
setText(markdown) {
this.content = markdown;
}
setColor(color) {
if (typeof color !== 'string') {
color = "FFF59D";
}
this.color = color;
}
setTitle(title) {
if (typeof title != 'string' || title.trim() == "") {
if (typeof this.content != 'string' || this.content.trim() == "") {
title = "New note";
} else {
title = this.content.split('\n')[0];
title = title.replace(/[\*#_`]/, "");
title = title.replace(/^- \[[x ]\] /i, "");
}
}
this.title = title.trim();
}
setModified(timestamp) {
if (typeof timestamp == 'undefined' || timestamp == null) {
this.setModified(Math.round(new Date().getTime() / 1000));
} else {
this.modified = timestamp;
}
}
setFavorite(favorite) {
this.favorite = favorite == true;
}
toChecklist() {
var text = this.getText().split('\n');
for (var i = 0; i < text.length; i++) {
if (text[i].match(/^[^\s\=\#\-<](.+)/)) {
if (text.length > i && text[i + 1].match(/^[\=-](.+)/)) {
// Don't do it if the next line makes this one a heading
continue;
}
text[i] = "- [ ] " + text[i];
}
}
this.setText(text.join("\n"));
}
fromChecklist() {
var text = this.getText();
this.setText(text.replace(/^- \[[ x]\] /im, ""));
}
toggleChecklistItem(item) {
item = item.trim();
var text = this.getText().split("\n");
var finalCheckState = false;
for (var i in text) {
var li = text[i].trim();
if (!li.match(/^- \[[x ]\] .*/i)) {
continue;
}
var linecleaned = li.replace(/^- \[[x ]\] /i, '').trim();
if (item != linecleaned) {
continue;
}
if (li.match(/^- \[[x]\] .*/i)) {
text[i] = li.replace(/^- \[[x]\] /i, "- [ ] ");
finalCheckState = false;
} else if (li.match(/^- \[[ ]\] .*/i)) {
text[i] = li.replace(/^- \[[ ]\] /i, "- [x] ");
finalCheckState = true;
}
}
this.setText(text.join("\n"));
return finalCheckState;
}
/**
* Returns true if the notes appear to be the same to the user.
* Does not check "hidden" attributes such as modified time.
* @param {Note} othernote
* @returns {boolean}
*/
compareTo(othernote) {
if (this.getText() != othernote.getText()) {
return false;
}
if (this.getFavorite() != othernote.getFavorite()) {
return false;
}
if (this.getColor() != othernote.getColor()) {
return false;
}
}
}

@ -0,0 +1,37 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class NotePostAPI {
constructor(server, username, password) {
this.server = server;
this.username = username;
this.password = password;
}
post(action, data, success, error) {
var self = this;
return $.ajax({
url: this.server + "/api/" + action,
dataType: "json",
method: "POST",
data: data,
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", "Basic " + btoa(self.username + ":" + self.password));
},
success: function (val) {
if (typeof success == 'function') {
success(val);
}
},
error: function () {
if (typeof error == 'function') {
error();
}
}
});
}
}

@ -1,187 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class NotePostNotes extends Notes {
constructor(server, username, password) {
super();
this.server = server;
this.username = username;
this.password = password;
}
del(noteid, success, error) {
super.del(noteid);
var self = this;
return $.ajax({
url: this.server + "/api/deletenote",
dataType: "json",
cache: false,
method: "POST",
data: {
id: noteid
},
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", "Basic " + btoa(self.username + ":" + self.password));
},
success: function (val) {
if (val.status == "OK") {
self.notes = val.notes;
}
if (typeof success == 'function') {
success();
}
},
error: function () {
if (typeof error == 'function') {
error();
}
}
});
}
add(note, success, error) {
note.norealid = true;
this.saveNote(note, success, error);
}
getNote(noteid, success, error) {
return $.ajax({
url: this.server + "/api/getnote",
dataType: "json",
method: "POST",
data: {
id: noteid
},
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", "Basic " + btoa(self.username + ":" + self.password));
},
success: function (val) {
if (val.status == "OK") {
if (typeof success == 'function') {
success(val.note);
}
} else {
if (typeof error == 'function') {
error(val.msg);
}
}
},
error: function () {
if (typeof error == 'function') {
error();
}
}
});
}
saveNote(note, success, error) {
var self = this;
var data = {
text: note.content,
color: note.color,
modified: note.modified,
favorite: note.favorite ? "1" : "0"
}; // Don't send ID if it's a locally-made note
if (note.norealid != true) {
data.id = note.noteid;
}
return $.ajax({
url: this.server + "/api/savenote",
dataType: "json",
method: "POST",
data: data,
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", "Basic " + btoa(self.username + ":" + self.password));
},
success: function (val) {
if (val.status == "OK") {
if (typeof success == 'function') {
success(val.note);
}
} else {
if (typeof error == 'function') {
error(val.msg);
}
}
},
error: function () {
if (typeof error == 'function') {
error();
}
}
});
}
/**
* Sync notes with the NotePost server, resolving conflicts in the process.
*
* @param {function} success(notes) called when everything's synced up.
* @param {function} error
* @returns {undefined}
*/
sync(success, error) {
super.sync();
var self = this;
$.ajax({
url: this.server + "/api/getnotes",
dataType: "json",
cache: false,
method: "POST",
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", "Basic " + btoa(self.username + ":" + self.password));
},
success: function (val) {
if (val.status == "OK") {
console.log("Comparing notes...");
console.log("Local copy:", self.notes);
console.log("Remote copy:", val.notes);
var delta = getDelta(self.notes, val.notes);
console.log("Comparison: ", delta);
var notes = delta.noChange;
notes = notes.concat(delta.addedRemote);
notes = notes.concat(delta.changedRemote); // Sync locally-created or modified notes
var notesToUpload = delta.addedLocal;
notesToUpload = notesToUpload.concat(delta.changedLocal);
var addedOrChangedLocallyAjax = [];
for (var i = 0; i < notesToUpload.length; i++) {
addedOrChangedLocallyAjax.push(self.saveNote(self.fix(notesToUpload[i]), function (n) {
notes.push(n);
}, function (err) {
if (typeof err === "string") {
app.dialog.alert(err, "Error");
}
}));
}
$.when(addedOrChangedLocallyAjax).always(function () {
self.notes = notes;
self.fixAll();
localStorage.setItem("notes", JSON.stringify(notes));
console.log(JSON.parse(localStorage.getItem("notes")));
}).then(function () {
if (typeof success == 'function') {
success(notes);
}
}, function () {
if (typeof error == 'function') {
error(notes);
}
});
}
},
error: function () {
if (typeof error == 'function') {
error();
}
}
});
}
}

@ -3,167 +3,252 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class Notes {
constructor() {
this.notes = [];
}
get(noteid) {
for (var i = 0; i < this.notes.length; i++) {
if (this.notes[i].noteid == noteid) {
return this.notes[i];
}
}
return null;
}
getAll() {
return this.notes;
}
set(note) {
for (var i = 0; i < this.notes.length; i++) {
if (this.notes[i].noteid == note.noteid) {
// Refresh HTML rendering
note.html = marked(note.content);
this.notes[i] = note;
this.save();
return;
}
}
this.notes.push(note);
this.save();
}
del(noteid, success, error) {
var newnotearr = [];
class Notes {
for (var i = 0; i < this.notes.length; i++) {
if (this.notes[i].noteid != noteid) {
newnotearr.push(this.notes[i]);
}
constructor() {
this.notes = [];
}
this.notes = newnotearr;
this.save();
if (typeof success == 'function') {
success();
get(noteid) {
this.loadAll();
for (var n in this.notes) {
if (this.notes[n].getID() == noteid) {
return this.notes[n];
}
}
return null;
}
}
add(note, success, error) {
var noteid = null;
do {
noteid = Math.floor(Math.random() * (9999999999 - 1000000000) + 1000000000);
console.log("Generating random note ID: " + noteid);
} while (this.get(noteid) != null);
note["noteid"] = noteid;
this.notes.push(note);
this.save();
if (typeof success == 'function') {
success(note);
getAll() {
this.loadAll();
return this.notes;
}
}
fix(note) {
if (typeof note.noteid !== 'number') {
note.noteid = null;
} // Set background color
if (typeof note.color !== 'string') {
note.color = "FFF59D";
} // Set text color based on background
if (typeof note.textcolor !== 'string') {
var r = parseInt(note.color.substring(0, 2), 16);
var g = parseInt(note.color.substring(2, 4), 16);
var b = parseInt(note.color.substring(4, 6), 16);
var contrast = Math.sqrt(r * r * 0.241 + g * g * 0.691 + b * b * 0.068);
if (contrast > 130) {
note.textcolor = "000000";
} else {
note.textcolor = "FFFFFF";
}
} // Just in case
if (typeof note.content !== 'string') {
note.content = "";
} // Set title
if (typeof note.title !== 'string') {
note.title = note.content.split('\n')[0].replace(/[#\-]+/gi, "").trim();
}
if (typeof note.modified !== 'number') {
note.modified = Math.round(new Date().getTime() / 1000);
} // Render Markdown to HTML
if (typeof note.html !== 'string') {
note.html = marked(note.content);
/**
* Save a Note into this Notes object, and save to localStorage.
* @param {type} note
* @returns {undefined}
*/
set(note) {
for (var n in this.notes) {
if (this.notes[n].getID() == note.getID()) {
this.notes[n] = note;
this.saveAll();
return;
}
}
this.notes.push(note);
this.saveAll();
}
return note;
}
fixAll() {
for (var i = 0; i < this.notes.length; i++) {
this.notes[i] = this.fix(this.notes[i]);
/**
* Load the notes from localStorage.
* @returns {undefined}
*/
loadAll() {
var thisnotes = [];
var notes = JSON.parse(localStorage.getItem("notes"));
console.log("notes JSON:", notes);
if (notes == null) {
notes = [];
}
for (var n in notes) {
var note = Note.loadNote(notes[n].noteid);
if (note != null) {
thisnotes.push(note);
}
}
this.notes = thisnotes;
console.log("Loading all notes: ", this.notes);
}
this.save();
}
/**
* Sync notes with the storage backend.
*
* @param {type} success
* @param {type} error
* @returns {undefined}
*/
sync(success, error) {
if (localStorage.getItem("notes") !== null) {
// There is localstorage
var storage = JSON.parse(localStorage.getItem("notes"));
if (typeof this.notes === 'undefined') {
this.notes = [];
}
console.log("Memory copy:", this.notes);
console.log("Storage copy:", storage);
var delta = getDelta(this.notes, storage);
console.log("Comparison: ", delta);
var notes = delta.noChange;
notes = notes.concat(delta.addedRemote);
notes = notes.concat(delta.changedRemote);
notes = notes.concat(delta.addedLocal);
notes = notes.concat(delta.changedLocal); // If localStorage is missing something, we still want it
notes = notes.concat(delta.deletedRemote);
this.notes = notes;
this.fixAll();
/**
* Save all notes to localStorage.
* @returns {undefined}
*/
saveAll() {
console.log("Saving all notes: ", this.notes);
localStorage.setItem("notes", "[]");
for (var n in this.notes) {
this.notes[n].saveNote();
}
this.loadAll();
}
this.save();
if (typeof success == 'function') {
success(this.notes);
/**
* Sync all notes to the remote and callback with the latest array of notes
* @param {function} callback
* @returns {undefined}
*/
syncAll(success, error) {
this.loadAll();
var self = this;
APICLIENT.post(
"getnotes",
{},
function (val) {
if (val.status == "OK") {
var localnotes = self.notes;
var remotenotes = [];
for (var n in val.notes) {
var no = new Note(val.notes[n].content, val.notes[n].color, val.notes[n].noteid);
no.setModified(val.notes[n].modified);
no.setFavorite(val.notes[n].favorite);
remotenotes.push(no);
}
function hasNoteID(array, id) {
for (var n in array) {
if (array[n].getID() == id) {
return true;
}
}
return false;
}
function getNoteByID(array, id) {
for (var n in array) {
if (array[n].getID() == id) {
return array[n];
}
}
}
console.log("localnotes:", localnotes);
console.log("remotenotes:", remotenotes);
// List of notes to delete on the server
var notesToDeleteRemote = [];
// List of notes to add on the server
var notesToAddRemote = [];
// List of notes to save/update on the server
var notesToSaveRemote = [];
// List of notes to save/update locally
var notesToSaveLocal = [];
// List of notes after syncing, should match on both sides
var notesListFinal = [];
for (var i in localnotes) {
var note = localnotes[i];
switch (note.getSyncStatus()) {
case "LOCAL_DELETED":
case "DELETED":
if (hasNoteID(remotenotes, note.getID())) {
notesToDeleteRemote.push(note);
}
break;
case "LOCAL_MODIFIED":
default:
if (hasNoteID(remotenotes, note.getID())) {
// The note exists remotely too
var remnote = getNoteByID(remotenotes, note.getID());
if (remnote.getModified() > note.getModified()) {
// Remote note is newer, keep it
notesToSaveLocal.push(remnote);
} else if (remnote.getModified() < note.getModified()) {
// Local note is newer, push it
console.log("remote note that's being overwritten: ", remnote);
console.log("local note that's overwriting it: ", note);
notesToSaveRemote.push(note);
} else {
if (note.compareTo(remnote) != true) {
// They aren't the same, let's trust the server
notesToSaveLocal.push(remnote);
}
}
} else {
if (note.getSyncStatus() != "NONE") {
notesToAddRemote.push(note);
}
}
break;
}
}
for (var i in remotenotes) {
var note = remotenotes[i];
if (!hasNoteID(localnotes, note.getID())) {
// The note is only on the remote, save it locally
notesToSaveLocal.push(note);
}
}
console.log("notesToDeleteRemote", notesToDeleteRemote);
console.log("notesToAddRemote", notesToAddRemote);
console.log("notesToSaveRemote", notesToSaveRemote);
console.log("notesToSaveLocal", notesToSaveLocal);
// Save notes locally
for (var i in notesToSaveLocal) {
notesListFinal.push(notesToSaveLocal[i]);
}
console.log("final before ajax: ", notesListFinal);
var ajaxOps = [];
// Delete notes on server
for (var i in notesToDeleteRemote) {
ajaxOps.push(notesToDeleteRemote[i].deleteOnNotePost(function (n) {
console.log("NotePost sync delete: ", n);
}, function (err) {
if (typeof err === "string") {
app.dialog.alert(err, "Error");
}
}));
}
// Add notes on server
for (var i in notesToAddRemote) {
ajaxOps.push(notesToAddRemote[i].saveToNotePost(function (n) {
notesListFinal.push(n);
console.log("NotePost sync add: ", n);
}, function (err) {
notesListFinal.push(notesToAddRemote[i]);
if (typeof err === "string") {
app.dialog.alert(err, "Error");
}
}, true));
}
// Save notes on server
for (var i in notesToSaveRemote) {
ajaxOps.push(notesToSaveRemote[i].saveToNotePost(function (n) {
notesListFinal.push(n);
console.log("NotePost sync save: ", n);
}, function (err) {
notesListFinal.push(notesToSaveRemote[i]);
if (typeof err === "string") {
app.dialog.alert(err, "Error");
}
}));
}
$.when(...ajaxOps).always(function () {
// success
console.log("final after sync: ", notesListFinal);
self.notes = notesListFinal;
self.saveAll();
self.loadAll();
if (typeof success == 'function') {
success(self.notes);
}
});
}
},
function () {
if (typeof error == 'function') {
error(self.notes);
}
});
}
}
save() {
localStorage.setItem("notes", JSON.stringify(this.notes));
}
}

@ -1,83 +0,0 @@
/*
* The code in this file is by StackOverflow user Juan Mendes.
* License: Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0).
* Source: https://stackoverflow.com/a/14966749
*/
/**
* Creates a map out of an array be choosing what property to key by
* @param {object[]} array Array that will be converted into a map
* @param {string} prop Name of property to key by
* @return {object} The mapped array. Example:
* mapFromArray([{a:1,b:2}, {a:3,b:4}], 'a')
* returns {1: {a:1,b:2}, 3: {a:3,b:4}}
*/
function mapFromArray(array, prop) {
var map = {};
for (var i = 0; i < array.length; i++) {
map[ array[i][prop] ] = array[i];
}
return map;
}
/**
* @param {object[]} o old array of notes (local copy)
* @param {object[]} n new array of notes (remote copy)
* @param {object} An object with changes
*/
function getDelta(o, n) {
var delta = {
addedRemote: [],
addedLocal: [],
deletedRemote: [],
deletedLocal: [],
changedRemote: [],
changedLocal: [],
noChange: []
};
oSane = [];
for (var i = 0; i < o.length; i++) {
if (o[i].noteid == null) { // Note has no real `noteid`
delta.addedLocal.push(o[i]);
} else {
oSane.push(o[i]);
}
}
var local = mapFromArray(oSane, 'noteid');
var remote = mapFromArray(n, 'noteid');
for (var id in local) {
if (!remote.hasOwnProperty(id)) { // Notes that are only present locally
delta.addedLocal.push(local[id]);
// TODO: Figure out which notes were actually added locally and which were deleted on the server
/*if (local[id].norealid) { // Note hasn't been synced to the remote yet
delta.addedLocal.push(local[id]);
} else { // Note has been synced to remote but isn't there anymore
delta.deletedRemote.push(local[id]);
}*/
} else { // Notes that are present on both
if (local[id].modified > remote[id].modified) { // Local copy is newer
delta.changedLocal.push(local[id]);
} else if (local[id].modified < remote[id].modified) { // Remote copy is newer
delta.changedRemote.push(remote[id]);
} else { // Modified date is same, let's check content
if (local[id].content == remote[id].content) {
delta.noChange.push(local[id]);
} else if (local[id].content.length > remote[id].content.length) {
delta.changedLocal.push(local[id]);
} else {
delta.changedRemote.push(remote[id]);
}
}
}
}
// Add notes that are only on the remote
for (var id in remote) {
if (!local.hasOwnProperty(id)) {
delta.addedRemote.push(remote[id]);
}
}
return delta;
}

@ -5,55 +5,38 @@
*/
function saveme(callback) {
function finishSave(note, callback) {
NOTES.fixAll();
NOTES.sync(function () {
app.toast.create({
text: 'Note saved.',
closeTimeout: 2000
}).open();
$("#orig_content").val(note.content);
if (typeof callback == "function") {
callback();
}
}, function () {
app.toast.create({
text: 'Something went wrong, your note might not be synced correctly.',
closeTimeout: 10000
}).open();
if (typeof callback == "function") {
callback();
}
});
}
sync(); // update textareas with correct content
sync();
var noteid = $("#note_content").data("noteid");
if (noteid == "") {
var note = {
content: $("#note_content").val(),
modified: Math.round((new Date()).getTime() / 1000)
};
NOTES.add(note, function (n) {
$("#note_content").data("noteid", n.noteid);
finishSave(n, callback);
}, function (err) {
if (typeof err == "string") {
app.dialog.alert(err, "Error");
} else {
app.dialog.alert("An unknown error occurred.", "Error");
}
if (typeof callback == "function") {
callback();
}
});
} else {
var note = NOTES.get(noteid);
note.content = $("#note_content").val();
note.modified = Math.round((new Date()).getTime() / 1000);
NOTES.set(note);
finishSave(note, callback);
var note = new Note();
if (noteid != "") {
note = NOTES.get(noteid);
if (note.getSyncStatus() != "LOCAL_ONLY") {
note.setSyncStatus("LOCAL_EDITED");
}
}
note.setText($("#note_content").val());
note.setModified();
NOTES.set(note);
NOTES.syncAll(function () {
app.toast.create({
text: 'Note saved.',
closeTimeout: 2000
}).open();
$("#orig_content").val(note.content);
if (typeof callback == "function") {
callback();
}
}, function () {
app.toast.create({
text: 'Note saved locally.',
closeTimeout: 2000
}).open();
$("#orig_content").val(note.content);
if (typeof callback == "function") {
callback();
}
});
}
function init() {

@ -30,92 +30,88 @@ $(".view-main").on("click", ".parsedown-task-list", function (e) {
if (e.target.nodeName != "INPUT") {
checked = !checked;
}
console.log(checkbox);
console.log(line);
console.log(checked);
var lines = note.content.split("\n");
var newcontent = "";
var checkedState = note.toggleChecklistItem(text);
for (i in lines) {
var li = lines[i].trim();
if (!li.match(/^- \[[x ]\] .*/i)) {
continue;
}
var linecleaned = li.replace(/^- \[[x ]\] /i, '').trim();
if (text != linecleaned) {
continue;
}
if (li.match(/^- \[[x]\] .*/i)) {
lines[i] = li.replace(/^- \[[x]\] /i, "- [ ] ");
line.addClass("parsedown-task-list-open");
line.removeClass("parsedown-task-list-close");
checkbox.prop("checked", false);
} else if (li.match(/^- \[[ ]\] .*/i)) {
lines[i] = li.replace(/^- \[[ ]\] /i, "- [x] ");
line.addClass("parsedown-task-list-close");
line.removeClass("parsedown-task-list-open");
checkbox.prop("checked", true);
}
}
note.setModified();
note.content = lines.join("\n");
note.modified = null;
note.html = null;
NOTES.set(note);
NOTES.syncAll();
NOTES.set(NOTES.fix(note));
if (checkedState) {
line.addClass("parsedown-task-list-close");
line.removeClass("parsedown-task-list-open");
checkbox.prop("checked", true);
} else {
line.addClass("parsedown-task-list-open");
line.removeClass("parsedown-task-list-close");
checkbox.prop("checked", false);
}
NOTES.sync();
});
function loadCards(callback) {
// Do it twice as a workaround for the stupid sync issue
NOTES.sync(function () {
NOTES.sync(function (notes) {
for (i in window.shuffleInstance.items) {
window.shuffleInstance.remove(window.shuffleInstance.items[i]);
}
$(".notecard-col").remove();
var trayitems = [];
for (n in notes) {
var note = notes[n];
$("#notecards-bin").append('<div class="col-100 tablet-50 desktop-33 notecard-col" id="notecard-col-' + note.noteid + '" data-favorite="' + (note.favorite ? "1" : "0") + '">'
+ '<div class="card notecard" id="notecard-' + note.noteid + '" data-id="' + note.noteid + '" data-favorite="' + (note.favorite ? "1" : "0") + '" data-bg="' + note.color + '" data-fg="' + note.textcolor + '" style="background-color: #' + note.color + '; color: #' + note.textcolor + ';">'
+ '<div class="editbtn">'
+ '<i class="material-icons">edit</i>'
+ '</div>'
+ '<div class="menubtn">'
+ '<i class="material-icons">more_vert</i>'
+ '</div>'
+ '<div class="card-content card-content-padding"><div class="btnswrapthing"></div>' + note.html + '</div>'
+ '</div>'
+ '</div>');
trayitems.push({
title: note.title,
id: note.noteid
});
}
$(".notecard .card-content ul li:has(input[type=checkbox])").addClass("parsedown-task-list");
$(".notecard .card-content ul li:has(input[type=checkbox]:checkbox:not(:checked))").addClass("parsedown-task-list-open");
$(".notecard .card-content ul li:has(input[type=checkbox]:checkbox:checked)").addClass("parsedown-task-list-close");
$(".parsedown-task-list input[type=checkbox]").removeAttr("disabled");
var noteElements = document.getElementsByClassName("notecard-col");
window.shuffleInstance.add(noteElements);
window.shuffleInstance.sort({
reverse: true,
by: function (el) {
return el.getAttribute("data-favorite");
}
});
setTrayMenu(trayitems);
if (typeof callback == 'function') {
callback();
}
}, function () {
restartApplication();
function loadNotesToCards(notes, callback) {
for (i in window.shuffleInstance.items) {
window.shuffleInstance.remove(window.shuffleInstance.items[i]);
}
$(".notecard-col").remove();
var trayitems = [];
for (n in notes) {
var note = notes[n];
// Ignore notes that we deleted but haven't synced yet
if (note.getSyncStatus() == "LOCAL_DELETED") {
continue;
}
$("#notecards-bin").append('<div class="col-100 tablet-50 desktop-33 notecard-col grid-item" id="notecard-col-' + note.noteid + '" data-favorite="' + (note.favorite ? "1" : "0") + '">'
+ '<div class="card notecard" id="notecard-' + note.getID() + '" data-id="' + note.getID() + '" data-favorite="' + (note.getFavorite() ? "1" : "0") + '" data-bg="' + note.getColor() + '" data-fg="' + note.getTextColor() + '" style="background-color: #' + note.getColor() + '; color: #' + note.getTextColor() + ';">'
+ '<div class="editbtn">'
+ '<i class="material-icons">edit</i>'
+ '</div>'
+ '<div class="menubtn">'
+ '<i class="material-icons">more_vert</i>'
+ '</div>'
+ '<div class="card-content card-content-padding"><div class="btnswrapthing"></div>' + note.getHTML() + '</div>'
+ '</div>'
+ '</div>');
trayitems.push({
title: note.getTitle(),
id: note.getID()
});
}, function () {
restartApplication();
}
$(".notecard .card-content ul li:has(input[type=checkbox])").addClass("parsedown-task-list");
$(".notecard .card-content ul li:has(input[type=checkbox]:checkbox:not(:checked))").addClass("parsedown-task-list-open");
$(".notecard .card-content ul li:has(input[type=checkbox]:checkbox:checked)").addClass("parsedown-task-list-close");
$(".parsedown-task-list input[type=checkbox]").removeAttr("disabled");
var noteElements = document.getElementsByClassName("notecard-col");
window.shuffleInstance.add(noteElements);
window.shuffleInstance.sort({
reverse: true,
by: function (el) {
if (el.getAttribute("id") == "offline-indicator") {
return "999999999";
}
return el.getAttribute("data-favorite");
}
});
setTrayMenu(trayitems);
if (typeof callback == 'function') {
callback();
}
}
function loadCards(callback) {
NOTES.syncAll(function (notes) {
if ($("#offline-indicator").css("display") != "none") {
app.toast.create({
text: 'Back online.',
closeTimeout: 2000
}).open();
}
$("#offline-indicator").css("display", "none");
loadNotesToCards(notes, callback);
}, function (notes) {
$("#offline-indicator").css("display", "");
loadNotesToCards(notes, callback);
});
}
@ -124,15 +120,19 @@ function editNote(id) {
router.navigate("/editnote", {
context: {
noteid: id,
content: note.content,
notetitle: note.title,
content: note.getText(),
notetitle: note.getTitle(),
}
});
console.log("Editing " + id);
}
function favoriteNote(id) {
var note = NOTES.get(id);
note.setFavorite(!note.getFavorite());
note.setModified();
$("#notecard-" + id).attr("data-favorite", note.getFavorite() ? "1" : "0");
note.saveNote();
}
function makeList(id) {
@ -141,10 +141,11 @@ function makeList(id) {
function deleteNote(id) {
app.dialog.confirm('Are you sure?', 'Delete Note', function () {
NOTES.del(id, function () {
window.shuffleInstance.remove(document.getElementById("notecard-" + id));
loadCards();
});
var note = NOTES.get(id);
note.deleteme();
NOTES.set(note);
window.shuffleInstance.remove(document.getElementById("notecard-" + id));
loadCards();
});
}
@ -186,18 +187,15 @@ $("#app").on("click", "#colorpicker .colorpicker-color", function () {
var noteid = $("#colorpicker").data("noteid");
var note = NOTES.get(noteid);
app.popup.close();
note.color = color;
// Set them to null, they'll be fixed in fix()
note.modified = null;
note.textcolor = null;
note2 = NOTES.fix(note);
NOTES.set(note2);
$("#notecard-" + noteid).data("bg", note2.color);
$("#notecard-" + noteid).data("fg", note2.textcolor);
$("#notecard-" + noteid).attr("data-bg", note2.color);
$("#notecard-" + noteid).attr("data-fg", note2.textcolor); // For CSS starbg
$("#notecard-" + noteid).css("background-color", "#" + note2.color);
$("#notecard-" + noteid).css("color", "#" + note2.textcolor);
note.setColor(color);
note.setModified();
note.saveNote();
$("#notecard-" + noteid).data("bg", note.getColor());
$("#notecard-" + noteid).data("fg", note.getTextColor());
$("#notecard-" + noteid).attr("data-bg", note.getColor());
$("#notecard-" + noteid).attr("data-fg", note.getTextColor()); // For CSS starbg
$("#notecard-" + noteid).css("background-color", "#" + note.getColor());
$("#notecard-" + noteid).css("color", "#" + note.getTextColor());
});
function openNoteActionMenu(notecard) {
@ -221,13 +219,13 @@ function openNoteActionMenu(notecard) {
}
},
// {
// text: "Favorite",
// icon: '<i class="fas fa-star fa-fw"></i>',
// onClick: function () {
// favoriteNote(noteid);
// }
// },
{
text: "Favorite",
icon: '<i class="fas fa-star-half-alt fa-fw"></i>',
onClick: function () {
favoriteNote(noteid);
}
},
// {
// text: "Make a List",
// icon: '<i class="fas fa-tasks fa-fw"></i>',
@ -256,7 +254,7 @@ function openNoteActionMenu(notecard) {
'<ul>' +
'<li><a class="list-button item-link edit-note-btn" data-note="' + noteid + '"><i class="fas fa-edit fa-fw"></i> Edit</a></li>' +
'<li><a class="list-button item-link color-note-btn" data-note="' + noteid + '"><i class="fas fa-palette fa-fw"></i> Color</a></li>' +
//'<li><a class="list-button item-link favorite-note-btn" data-note="' + noteid + '"><i class="fas fa-star fa-fw"></i> Favorite</a></li>' +
'<li><a class="list-button item-link favorite-note-btn" data-note="' + noteid + '"><i class="fas fa-star-half-alt fa-fw"></i> Favorite</a></li>' +
//'<li><a class="list-button item-link listify-note-btn" data-note="' + noteid + '"><i class="fas fa-tasks fa-fw"></i> Make a List</a></li>' +
'<li><a class="list-button item-link delete-note-btn" data-note="' + noteid + '"><i class="fas fa-trash fa-fw"></i> Delete</a></li>' +
'</ul>' +

@ -25,6 +25,7 @@ var mainView = app.views.create('.view-main', {
var router = mainView.router;
var NOTES = null;
var APICLIENT = null;
var OFFLINE = false;
@ -68,8 +69,8 @@ if (localStorage.getItem("configured") == null) {
// Open the setup page
router.navigate("/setup/0");
} else {
createNotesObject(function (n) {
NOTES = n;
router.navigate("/home");
});
APICLIENT = new NotePostAPI(localStorage.getItem("serverurl"), localStorage.getItem("username"), localStorage.getItem("password"));
NOTES = new Notes();
NOTES.loadAll();
router.navigate("/home");
}

@ -1,36 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
function createNotesObject(callback) {
if (localStorage.getItem("serverurl") == null) {
callback(new Notes());
} else {
var checkurl = localStorage.getItem("serverurl") + "/api/ping";
$.ajax({
url: checkurl,
dataType: "json",
cache: false,
method: "POST",
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", "Basic " + btoa(localStorage.getItem("username") + ":" + localStorage.getItem("password")));
}, success: function (data) {
if (data.status == "OK") {
callback(new NotePostNotes(localStorage.getItem("serverurl"), localStorage.getItem("username"), localStorage.getItem("password")));
} else if (data.status == "ERROR") {
app.dialog.alert(data.msg, "Error");
OFFLINE = true;
callback(new Notes());
} else {
OFFLINE = true;
callback(new Notes());
}
}, error: function () {
OFFLINE = true;
callback(new Notes());
}
});
}
}

@ -101,7 +101,7 @@ function initNW() {
if (items.length > 0) {
for (i in items) {
console.log(items[i]);
//console.log(items[i]);
var label_max = 50;
var label = items[i].title;
if (label.length > label_max) {

@ -43,8 +43,7 @@
<div class="row notecards-row" id="notecards-bin">
<div class="col-100 tablet-50 desktop-33" style="visibility: hidden;" id="notecard-col-sizer">
</div>
{{#if offline}}
<div class="col-100 tablet-50 desktop-33 notecard-col">
<div class="col-100 tablet-50 desktop-33 grid-item" style="display: none;" id="offline-indicator">
<div class="card">
<div class="card-content card-content-padding text-align-center text-color-gray">
<div><i class="material-icons" style="font-size: 40pt;">cloud_off</i></div>
@ -52,20 +51,6 @@
</div>
</div>
</div>
{{/if}}
{{#each notecards}}
<div class="col-100 tablet-50 desktop-33 notecard-col" id="notecard-col-{{noteid}}" data-favorite="{{favorite}}">
<div class="card notecard" id="notecard-{{noteid}}" data-id="{{noteid}}" data-favorite="{{favorite}}" data-bg="{{color}}" data-fg="{{textcolor}}" style="background-color: #{{color}}; color: #{{textcolor}};">
<div class="editbtn">
<i class="material-icons">edit</i>
</div>
<div class="menubtn">
<i class="material-icons">more_vert</i>
</div>
<div class="card-content card-content-padding"><div class="btnswrapthing"></div>{{html}}</div>
</div>
</div>
{{/each}}
</div>
<!-- Make sure the bottom note won't be blocked by the floating button -->
@ -105,7 +90,7 @@
<script>
window.shuffleInstance = new window.Shuffle(document.getElementById('notecards-bin'), {
itemSelector: '.notecard-col',
itemSelector: '.grid-item',
sizer: '#notecard-col-sizer'
});

@ -8,15 +8,10 @@
var routes = [
{
path: '/home',
templateUrl: './pages/home.html',
name: 'home',
async: function (routeTo, routeFrom, resolve, reject) {
var pageinfo = {
templateUrl: './pages/home.html',
reloadCurrent: (routeFrom.name == "home")
};
var context = {
showrefreshbtn: (platform_type != "cordova"),
offline: OFFLINE,
options: {
context: {
colors: [
// Pastel colors
"EF9A9A", // red
@ -48,26 +43,7 @@ var routes = [
"795548", // brown
"000000" // black
]
};
NOTES.sync(function (notes) {
context["notecards"] = notes;
if (routeFrom.name == "home") {
app.ptr.done();
}
resolve(pageinfo, {
context: context
});
}, function () {
NOTES.fixAll();
context["notecards"] = NOTES.getAll();
if (routeFrom.name == "home") {
app.ptr.done();
}
context["offline"] = true;
resolve(pageinfo, {
context: context
});
});
}
}
},
{

Chargement…
Annuler
Enregistrer