Improved debouncing for plain editor

This commit is contained in:
Mo Bitar
2019-05-31 19:44:29 -05:00
parent 04a916e5a3
commit bb282f868f
2 changed files with 40 additions and 64 deletions

View File

@@ -183,7 +183,7 @@ angular.module('app')
}
if(oldNote && oldNote != note) {
if(oldNote.hasChanges) {
if(oldNote.dirty) {
this.saveNote(oldNote);
} else if(oldNote.dummy) {
this.remove()(oldNote);
@@ -270,12 +270,23 @@ angular.module('app')
this.showMenu = false;
}
// var statusTimeout;
this.EditorNgDebounce = 200;
const SyncDebouce = 1000;
const SyncNoDebounce = 100;
this.saveNote = function({bypassDebouncer, updateClientModified, dontUpdatePreviews}) {
let note = this.note;
note.dummy = false;
if(!modelManager.findItem(note.uuid)) {
alert("The note you are attempting to save can not be found or has been deleted. Changes you make will not be synced. Please copy this note's text and start a new note.");
return;
}
this.saveNote = function(note, callback, updateClientModified, dontUpdatePreviews) {
// We don't want to update the client modified date if toggling lock for note.
modelManager.setItemDirty(note, true, updateClientModified);
this.showSavingStatus();
if(!dontUpdatePreviews) {
let limit = 80;
var text = note.text || "";
@@ -286,53 +297,20 @@ angular.module('app')
note.content.preview_html = null;
}
this.showSavingStatus();
syncManager.sync().then((response) => {
if(response && response.error) {
if(!this.didShowErrorAlert) {
if(this.saveTimeout) {
$timeout.cancel(this.saveTimeout);
}
let syncDebouceMs = bypassDebouncer ? SyncNoDebounce : SyncDebouce;
this.saveTimeout = $timeout(() => {
syncManager.sync().then((response) => {
if(response && response.error && !this.didShowErrorAlert) {
this.didShowErrorAlert = true;
alert("There was an error saving your note. Please try again.");
}
$timeout(() => {
callback && callback(false);
})
} else {
note.hasChanges = false;
$timeout(() => {
callback && callback(true);
});
}
})
}
let saveTimeout;
this.changesMade = function({bypassDebouncer, updateClientModified, dontUpdatePreviews} = {}) {
let note = this.note;
note.dummy = false;
/* In the case of keystrokes, saving should go through a debouncer to avoid frequent calls.
In the case of deleting or archiving a note, it should happen immediately before the note is switched out
*/
let delay = bypassDebouncer ? 0 : 275;
// In the case of archiving a note, the note is saved immediately, then switched to another note.
// Usually note.hasChanges is set back to false after the saving delay, but in this case, because there is no delay,
// we set it to false immediately so that it is not saved twice: once now, and the other on setNote in oldNote.hasChanges.
note.hasChanges = bypassDebouncer ? false : true;
if(saveTimeout) $timeout.cancel(saveTimeout);
// if(statusTimeout) $timeout.cancel(statusTimeout);
saveTimeout = $timeout(() => {
note.dummy = false;
// Make sure the note exists. A safety measure, as toggling between tags triggers deletes for dummy notes.
// Race conditions have been fixed, but we'll keep this here just in case.
if(!modelManager.findItem(note.uuid)) {
alert("The note you are attempting to save can not be found or has been deleted. Changes you make will not be synced. Please copy this note's text and start a new note.");
return;
}
this.saveNote(note, null, updateClientModified, dontUpdatePreviews);
}, delay)
})
}, syncDebouceMs)
}
this.showSavingStatus = function() {
@@ -380,8 +358,7 @@ angular.module('app')
}
this.contentChanged = function() {
// content changes should bypass manual debouncer as we use the built in ng-model-options debouncer
this.changesMade({bypassDebouncer: true, updateClientModified: true});
this.saveNote({updateClientModified: true});
}
this.onTitleEnter = function($event) {
@@ -391,7 +368,7 @@ angular.module('app')
}
this.onTitleChange = function() {
this.changesMade({dontUpdatePreviews: true, updateClientModified: true});
this.saveNote({dontUpdatePreviews: true, updateClientModified: true});
}
this.onNameFocus = function() {
@@ -428,7 +405,7 @@ angular.module('app')
this.remove()(this.note);
} else {
this.note.content.trashed = true;
this.changesMade({bypassDebouncer: true, dontUpdatePreviews: true});
this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true});
}
this.showMenu = false;
}
@@ -446,7 +423,7 @@ angular.module('app')
this.restoreTrashedNote = function() {
this.note.content.trashed = false;
this.changesMade({bypassDebouncer: true, dontUpdatePreviews: true});
this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true});
}
this.deleteNotePermanantely = function() {
@@ -467,17 +444,17 @@ angular.module('app')
this.togglePin = function() {
this.note.setAppDataItem("pinned", !this.note.pinned);
this.changesMade({bypassDebouncer: true, dontUpdatePreviews: true});
this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true});
}
this.toggleLockNote = function() {
this.note.setAppDataItem("locked", !this.note.locked);
this.changesMade({bypassDebouncer: true, dontUpdatePreviews: true});
this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true});
}
this.toggleProtectNote = function() {
this.note.content.protected = !this.note.content.protected;
this.changesMade({bypassDebouncer: true, dontUpdatePreviews: true});
this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true});
// Show privilegesManager if Protection is not yet set up
privilegesManager.actionHasPrivilegesConfigured(PrivilegesManager.ActionViewProtectedNotes).then((configured) => {
@@ -489,12 +466,12 @@ angular.module('app')
this.toggleNotePreview = function() {
this.note.content.hidePreview = !this.note.content.hidePreview;
this.changesMade({bypassDebouncer: true, dontUpdatePreviews: true});
this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true});
}
this.toggleArchiveNote = function() {
this.note.setAppDataItem("archived", !this.note.archived);
this.changesMade({bypassDebouncer: true, dontUpdatePreviews: true});
this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true});
$rootScope.$broadcast("noteArchived");
}
@@ -726,11 +703,9 @@ angular.module('app')
this.removeTag(tag);
}
else if(action === "save-items" || action === "save-success" || action == "save-error") {
else if(action === "save-items") {
if(data.items.map((item) => {return item.uuid}).includes(this.note.uuid)) {
if(action == "save-items") {
this.showSavingStatus();
}
this.showSavingStatus();
}
}
}});
@@ -898,7 +873,7 @@ angular.module('app')
$timeout(() => {
this.note.text = editor.value;
this.changesMade({bypassDebouncer: true});
this.saveNote({bypassDebouncer: true});
})
},
})

View File

@@ -79,7 +79,8 @@
%textarea.editable#note-text-editor{"ng-if" => "!ctrl.selectedEditor", "ng-model" => "ctrl.note.text", "ng-readonly" => "ctrl.note.locked",
"ng-change" => "ctrl.contentChanged()", "ng-trim" => "false", "ng-click" => "ctrl.clickedTextArea()",
"ng-focus" => "ctrl.onContentFocus()", "dir" => "auto", "ng-attr-spellcheck" => "{{ctrl.spellcheck}}", "ng-model-options"=>"{ debounce: 300 }"}
"ng-focus" => "ctrl.onContentFocus()", "dir" => "auto", "ng-attr-spellcheck" => "{{ctrl.spellcheck}}",
"ng-model-options"=>"{ debounce: ctrl.EditorNgDebounce }"}
{{ctrl.onSystemEditorLoad()}}
%panel-resizer{"ng-if" => "ctrl.marginResizersEnabled", "panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish", "control" => "ctrl.rightResizeControl", "min-width" => 300, "hoverable" => "true", "property" => "'right'"}