From 98e4f95a816cd47c1cb14c92a4a9034ee022c4cc Mon Sep 17 00:00:00 2001 From: Matt Enlow Date: Wed, 8 Oct 2014 08:53:20 -0600 Subject: [PATCH] Autosave Draft posts Ref #1413 - If the user stops typing for 5 seconds, a "typingPause" action is sent from the GhCodemirrorComponent - Editor-base-controller ties the "typingPause" action to its "autosave" - If a post is being saved and does not have a title, the title is set to "(Untitled)" - Cleanup editor base controller property dependencies and code --- ghost/admin/components/gh-codemirror.js | 11 ++-- ghost/admin/controllers/editor/new.js | 4 +- ghost/admin/mixins/editor-base-controller.js | 65 +++++++++++--------- ghost/admin/templates/editor/edit.hbs | 5 +- 4 files changed, 47 insertions(+), 38 deletions(-) diff --git a/ghost/admin/components/gh-codemirror.js b/ghost/admin/components/gh-codemirror.js index cfc2204fe5..93ffed4c1d 100644 --- a/ghost/admin/components/gh-codemirror.js +++ b/ghost/admin/components/gh-codemirror.js @@ -9,19 +9,20 @@ codeMirrorShortcuts.init(); var onChangeHandler = function (cm, changeObj) { var line, - component = cm.component, - checkLine = _.bind(component.checkLine, component), - checkMarkers = _.bind(component.checkMarkers, component); + component = cm.component; // fill array with a range of numbers for (line = changeObj.from.line; line < changeObj.from.line + changeObj.text.length; line += 1) { - checkLine(line, changeObj.origin); + component.checkLine(line, changeObj.origin); } // Is this a line which may have had a marker on it? - checkMarkers(); + component.checkMarkers(); cm.component.set('value', cm.getValue()); + + // Send an action notifying a 5 second pause in typing/changes. + Ember.run.debounce(component, 'sendAction', 'typingPause', 5000); }; var onScrollHandler = function (cm) { diff --git a/ghost/admin/controllers/editor/new.js b/ghost/admin/controllers/editor/new.js index 73ba0daf4a..3a1e0735df 100644 --- a/ghost/admin/controllers/editor/new.js +++ b/ghost/admin/controllers/editor/new.js @@ -5,9 +5,9 @@ var EditorNewController = Ember.ObjectController.extend(EditorControllerMixin, { /** * Redirect to editor after the first save */ - save: function () { + save: function (options) { var self = this; - this._super().then(function (model) { + return this._super(options).then(function (model) { if (model.get('id')) { self.transitionToRoute('editor.edit', model); } diff --git a/ghost/admin/mixins/editor-base-controller.js b/ghost/admin/mixins/editor-base-controller.js index 11ae6049f9..71ecfde164 100644 --- a/ghost/admin/mixins/editor-base-controller.js +++ b/ghost/admin/mixins/editor-base-controller.js @@ -5,17 +5,13 @@ import boundOneWay from 'ghost/utils/bound-one-way'; // this array will hold properties we need to watch // to know if the model has been changed (`controller.isDirty`) -var watchedProps = ['scratch', 'titleScratch', 'model.isDirty']; +var watchedProps = ['scratch', 'titleScratch', 'model.isDirty', 'tags.[]']; Ember.get(PostModel, 'attributes').forEach(function (name) { watchedProps.push('model.' + name); }); -// watch if number of tags changes on the model -watchedProps.push('tags.[]'); - var EditorControllerMixin = Ember.Mixin.create(MarkerManager, { - needs: ['post-tags-input'], init: function () { @@ -34,11 +30,14 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, { */ willPublish: boundOneWay('isPublished'), + // Make sure editor starts with markdown shown + isPreview: false, + // set by the editor route and `isDirty`. useful when checking // whether the number of tags has changed for `isDirty`. previousTagNames: null, - tagNames: Ember.computed('tags.[]', function () { + tagNames: Ember.computed('tags.@each.name', function () { return this.get('tags').mapBy('name'); }), @@ -130,11 +129,7 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, { // which does *not* change the model's `isDirty` property, // `isDirty` will tell us if the other props have changed, // as long as the model is not new (model.isNew === false). - if (model.get('isDirty')) { - return true; - } - - return false; + return model.get('isDirty'); })), // used on window.onbeforeunload @@ -152,12 +147,12 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, { errors: { post: { published: { - 'published': 'Update failed.', - 'draft': 'Saving failed.' + published: 'Update failed.', + draft: 'Saving failed.' }, draft: { - 'published': 'Publish failed.', - 'draft': 'Saving failed.' + published: 'Publish failed.', + draft: 'Saving failed.' } } @@ -166,12 +161,12 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, { success: { post: { published: { - 'published': 'Updated.', - 'draft': 'Saved.' + published: 'Updated.', + draft: 'Saved.' }, draft: { - 'published': 'Published!', - 'draft': 'Saved.' + published: 'Published!', + draft: 'Saved.' } } } @@ -191,16 +186,15 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, { this.notifications.showError(message, { delayed: delay }); }, - shouldFocusTitle: Ember.computed('model', function () { - return !!this.get('model.isNew'); - }), + shouldFocusTitle: Ember.computed.alias('model.isNew'), actions: { - save: function () { + save: function (options) { var status = this.get('willPublish') ? 'published' : 'draft', prevStatus = this.get('status'), isNew = this.get('isNew'), self = this; + options = options || {}; self.notifications.closePassive(); @@ -210,15 +204,23 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, { // Set the properties that are indirected // set markdown equal to what's in the editor, minus the image markers. this.set('markdown', this.getMarkdown().withoutMarkers); - this.set('title', this.get('titleScratch')); this.set('status', status); + // Set a default title + if (!this.get('titleScratch')) { + this.set('titleScratch', '(Untitled)'); + } + this.set('title', this.get('titleScratch')); + return this.get('model').save().then(function (model) { - self.showSaveNotification(prevStatus, model.get('status'), isNew ? true : false); + if (!options.silent) { + self.showSaveNotification(prevStatus, model.get('status'), isNew ? true : false); + } return model; }).catch(function (errors) { - self.showErrorNotification(prevStatus, self.get('status'), errors); - + if (!options.silent) { + self.showErrorNotification(prevStatus, self.get('status'), errors); + } self.set('status', prevStatus); return Ember.RSVP.reject(errors); @@ -286,11 +288,14 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, { editor.replaceSelection(result_src); }, - // Make sure editor starts with markdown shown - isPreview: false, - togglePreview: function (preview) { this.set('isPreview', preview); + }, + + autoSave: function () { + if (this.get('model.isDraft')) { + this.send('save', {silent: true, disableNProgress: true}); + } } } }); diff --git a/ghost/admin/templates/editor/edit.hbs b/ghost/admin/templates/editor/edit.hbs index 075eda1cdd..4946cea1f9 100644 --- a/ghost/admin/templates/editor/edit.hbs +++ b/ghost/admin/templates/editor/edit.hbs @@ -16,7 +16,10 @@
- {{gh-codemirror value=scratch scrollInfo=view.markdownScrollInfo setCodeMirror="setCodeMirror" openModal="openModal"}} + {{gh-codemirror value=scratch scrollInfo=view.markdownScrollInfo + setCodeMirror="setCodeMirror" + openModal="openModal" + typingPause="autoSave"}}