From 4201def4ee07bb68535668ecd522db947e183bc1 Mon Sep 17 00:00:00 2001 From: Skylar Ittner Date: Wed, 23 Jan 2019 20:35:28 -0700 Subject: [PATCH] Refactor note code: close #3, fix #5, fix #6 --- www/css/notecards.css | 1 + www/index.html | 6 +- www/js/Note.class.js | 304 +++++++++++++++++++++++++++ www/js/NotePostAPI.class.js | 37 ++++ www/js/NotePostNotes.class.js | 187 ----------------- www/js/Notes.class.js | 377 +++++++++++++++++++++------------- www/js/delta.js | 83 -------- www/js/editnote.js | 75 +++---- www/js/home.js | 208 ++++++++++--------- www/js/main.js | 9 +- www/js/notes.js | 36 ---- www/js/platform.js | 2 +- www/pages/home.html | 19 +- www/routes.js | 32 +-- 14 files changed, 719 insertions(+), 657 deletions(-) create mode 100644 www/js/Note.class.js create mode 100644 www/js/NotePostAPI.class.js delete mode 100644 www/js/NotePostNotes.class.js delete mode 100644 www/js/delta.js delete mode 100644 www/js/notes.js diff --git a/www/css/notecards.css b/www/css/notecards.css index ddf9370..4025908 100644 --- a/www/css/notecards.css +++ b/www/css/notecards.css @@ -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); } diff --git a/www/index.html b/www/index.html index 376fde7..805a839 100644 --- a/www/index.html +++ b/www/index.html @@ -28,11 +28,9 @@ - - + + - - diff --git a/www/js/Note.class.js b/www/js/Note.class.js new file mode 100644 index 0000000..d82a587 --- /dev/null +++ b/www/js/Note.class.js @@ -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; + } + } + +} \ No newline at end of file diff --git a/www/js/NotePostAPI.class.js b/www/js/NotePostAPI.class.js new file mode 100644 index 0000000..0eac788 --- /dev/null +++ b/www/js/NotePostAPI.class.js @@ -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(); + } + } + }); + } +} \ No newline at end of file diff --git a/www/js/NotePostNotes.class.js b/www/js/NotePostNotes.class.js deleted file mode 100644 index b81c4e8..0000000 --- a/www/js/NotePostNotes.class.js +++ /dev/null @@ -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(); - } - } - }); - } - -} \ No newline at end of file diff --git a/www/js/Notes.class.js b/www/js/Notes.class.js index 1d94155..034a725 100644 --- a/www/js/Notes.class.js +++ b/www/js/Notes.class.js @@ -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)); - } } \ No newline at end of file diff --git a/www/js/delta.js b/www/js/delta.js deleted file mode 100644 index f39d2a0..0000000 --- a/www/js/delta.js +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/www/js/editnote.js b/www/js/editnote.js index 95205bf..52099b3 100644 --- a/www/js/editnote.js +++ b/www/js/editnote.js @@ -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() { diff --git a/www/js/home.js b/www/js/home.js index 2df729e..5bb8fd9 100644 --- a/www/js/home.js +++ b/www/js/home.js @@ -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('
' - + '
' - + '
' - + 'edit' - + '
' - + '' - + '
' + note.html + '
' - + '
' - + '
'); - 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('
' + + '
' + + '
' + + 'edit' + + '
' + + '' + + '
' + note.getHTML() + '
' + + '
' + + '
'); + 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: '', -// onClick: function () { -// favoriteNote(noteid); -// } -// }, + { + text: "Favorite", + icon: '', + onClick: function () { + favoriteNote(noteid); + } + }, // { // text: "Make a List", // icon: '', @@ -256,7 +254,7 @@ function openNoteActionMenu(notecard) { '' + diff --git a/www/js/main.js b/www/js/main.js index 1706ae0..0165af3 100644 --- a/www/js/main.js +++ b/www/js/main.js @@ -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"); } \ No newline at end of file diff --git a/www/js/notes.js b/www/js/notes.js deleted file mode 100644 index 15191a9..0000000 --- a/www/js/notes.js +++ /dev/null @@ -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()); - } - }); - } -} \ No newline at end of file diff --git a/www/js/platform.js b/www/js/platform.js index b99dd32..b365f44 100644 --- a/www/js/platform.js +++ b/www/js/platform.js @@ -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) { diff --git a/www/pages/home.html b/www/pages/home.html index 97a2afb..f68fefc 100644 --- a/www/pages/home.html +++ b/www/pages/home.html @@ -43,8 +43,7 @@
- {{#if offline}} -
+ - {{/if}} - {{#each notecards}} -
-
-
- edit -
- -
{{html}}
-
-
- {{/each}}
@@ -105,7 +90,7 @@