From a9ce31ffe733a3581501df80c03eaffbf74223e3 Mon Sep 17 00:00:00 2001 From: Matt Enlow Date: Sat, 15 Nov 2014 17:05:28 -0700 Subject: [PATCH] Fix high latency autosave bug in editor.new Closes #4400 - Added focusCursorAtEnd property to codemirror component, to set the cursor at document end on load. Used for posts that have been edited - centralized codemirror init code in component --- core/client/components/gh-codemirror.js | 36 +++++++++++++++-------- core/client/mixins/editor-base-route.js | 39 +++++++++++++++---------- core/client/templates/editor/edit.hbs | 2 +- 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/core/client/components/gh-codemirror.js b/core/client/components/gh-codemirror.js index b59b2f0635..89c0679a5f 100644 --- a/core/client/components/gh-codemirror.js +++ b/core/client/components/gh-codemirror.js @@ -42,9 +42,10 @@ onScrollHandler = function (cm) { Codemirror = Ember.TextArea.extend(MarkerManager, { focus: true, + focusCursorAtEnd: false, setFocus: function () { - if (this.focus) { + if (this.get('focus')) { this.$().val(this.$().val()).focus(); } }.on('didInsertElement'), @@ -54,24 +55,28 @@ Codemirror = Ember.TextArea.extend(MarkerManager, { }, afterRenderEvent: function () { - var self = this; - - function initMarkers() { - self.initMarkers.apply(self, arguments); - } + var codemirror; // replaces CodeMirror with TouchEditor only if we're on mobile mobileCodeMirror.createIfMobile(); - this.initCodemirror(); - this.codemirror.eachLine(initMarkers); + codemirror = this.initCodemirror(); + this.set('codemirror', codemirror); + this.sendAction('setCodeMirror', this); + + if (this.get('focus') && this.get('focusCursorAtEnd')) { + codemirror.execCommand('goDocEnd'); + } }, // this needs to be placed on the 'afterRender' queue otherwise CodeMirror gets wonky initCodemirror: function () { // create codemirror - var codemirror = CodeMirror.fromTextArea(this.get('element'), { + var codemirror, + self = this; + + codemirror = CodeMirror.fromTextArea(this.get('element'), { mode: 'gfm', tabMode: 'indent', tabindex: '2', @@ -92,7 +97,10 @@ Codemirror = Ember.TextArea.extend(MarkerManager, { } }); - codemirror.component = this; // save reference to this + // Codemirror needs a reference to the component + // so that codemirror originating events can propogate + // up the ember action pipeline + codemirror.component = this; // propagate changes to value property codemirror.on('change', onChangeHandler); @@ -106,10 +114,14 @@ Codemirror = Ember.TextArea.extend(MarkerManager, { })); codemirror.on('focus', function () { - codemirror.component.sendAction('onFocusIn'); + self.sendAction('onFocusIn'); }); - this.set('codemirror', codemirror); + codemirror.eachLine(function initMarkers() { + self.initMarkers.apply(self, arguments); + }); + + return codemirror; }, disableCodeMirror: function () { diff --git a/core/client/mixins/editor-base-route.js b/core/client/mixins/editor-base-route.js index 26bc1b3dbf..ab577ac881 100644 --- a/core/client/mixins/editor-base-route.js +++ b/core/client/mixins/editor-base-route.js @@ -32,30 +32,39 @@ var EditorBaseRoute = Ember.Mixin.create(styleBody, ShortcutsRoute, loadingIndic willTransition: function (transition) { var controller = this.get('controller'), - isDirty = controller.get('isDirty'), - + scratch = controller.get('scratch'), + controllerIsDirty = controller.get('isDirty'), model = controller.get('model'), - isNew = model.get('isNew'), - isSaving = model.get('isSaving'), - isDeleted = model.get('isDeleted'), - modelIsDirty = model.get('isDirty'); + state = model.getProperties('isDeleted', 'isSaving', 'isDirty', 'isNew'), + fromNewToEdit, + deletedWithoutChanges; + + fromNewToEdit = this.get('routeName') === 'editor.new' && + transition.targetName === 'editor.edit' && + transition.intent.contexts && + transition.intent.contexts[0] && + transition.intent.contexts[0].id === model.get('id'); + + deletedWithoutChanges = state.isDeleted && + (state.isSaving || !state.isDirty); this.send('closeSettingsMenu'); - // when `isDeleted && isSaving`, model is in-flight, being saved - // to the server. when `isDeleted && !isSaving && !modelIsDirty`, - // the record has already been deleted and the deletion persisted. - // - // in either case we can probably just transition now. - // in the former case the server will return the record, thereby updating it. - // @TODO: this will break if the model fails server-side validation. - if (!(isDeleted && isSaving) && !(isDeleted && !isSaving && !modelIsDirty) && isDirty) { + if (!fromNewToEdit && !deletedWithoutChanges && controllerIsDirty) { transition.abort(); this.send('openModal', 'leave-editor', [controller, transition]); return; } - if (isNew) { + // The controller may hold model state that will be lost in the transition, + // so we need to apply it now. + if (fromNewToEdit && controllerIsDirty) { + if (scratch !== model.get('markdown')) { + model.set('markdown', scratch); + } + } + + if (state.isNew) { model.deleteRecord(); } diff --git a/core/client/templates/editor/edit.hbs b/core/client/templates/editor/edit.hbs index 421e12a92e..f54e84514f 100644 --- a/core/client/templates/editor/edit.hbs +++ b/core/client/templates/editor/edit.hbs @@ -19,7 +19,7 @@
{{gh-codemirror value=scratch scrollInfo=view.markdownScrollInfo setCodeMirror="setCodeMirror" openModal="openModal" typingPause="autoSave" - focus=shouldFocusEditor onFocusIn="autoSaveNew"}} + focus=shouldFocusEditor focusCursorAtEnd=model.isDirty onFocusIn="autoSaveNew"}}