🎨 Focus editor content area by default (#768)
closes TryGhost/Ghost#8525 - always give focus to the editor content area by default when loading the editor - allow content autosave to work for new posts (it was previously turned off for new posts) - move transition-on-save behaviour from editor/new controller into the controller mixin's save routine - cancel background autosave when "are you sure you want to leave?" modal is shown as it can cause the "leave" option to fail because it attempts to delete the post record that can be in flight (plus if we're saving anyway it doesn't make much sense to ask the user 🙈) - this is quite an edge-case as it will only happen if the user makes a content change to a draft post then tries to leave the screen within 3 seconds - change the editor placeholder text - wait for any save task to finish before exiting the new post route (fixes infinite loop and popup of "are you sure you want to leave?" modal that is then closed automatically straight away - add a guard to the `gh-post-settings-menu` component so that if the authors query takes a while we don't end up trying to set a value when the component has already been removed
This commit is contained in:
parent
1596721468
commit
a0af248df4
@ -40,7 +40,9 @@ export default Component.extend(SettingsMenuMixin, {
|
||||
this._super(...arguments);
|
||||
|
||||
this.get('store').query('user', {limit: 'all'}).then((users) => {
|
||||
this.set('authors', users.sortBy('name'));
|
||||
if (!this.get('isDestroyed')) {
|
||||
this.set('authors', users.sortBy('name'));
|
||||
}
|
||||
});
|
||||
|
||||
this.get('model.author').then((author) => {
|
||||
|
@ -48,8 +48,10 @@ export default OneWayTextarea.extend(TextInputMixin, {
|
||||
|
||||
// collapse the element first so that we can shrink as well as expand
|
||||
// then set the height to match the text height
|
||||
el.style.height = 0;
|
||||
el.style.height = `${el.scrollHeight}px`;
|
||||
if (el) {
|
||||
el.style.height = 0;
|
||||
el.style.height = `${el.scrollHeight}px`;
|
||||
}
|
||||
},
|
||||
|
||||
_setupAutoExpand() {
|
||||
|
@ -1,23 +1,6 @@
|
||||
import Controller from 'ember-controller';
|
||||
import EditorControllerMixin from 'ghost-admin/mixins/editor-base-controller';
|
||||
|
||||
function K() {
|
||||
return this;
|
||||
}
|
||||
|
||||
export default Controller.extend(EditorControllerMixin, {
|
||||
// Overriding autoSave on the base controller, as the new controller shouldn't be autosaving
|
||||
autoSave: K,
|
||||
actions: {
|
||||
/**
|
||||
* Redirect to editor after the first save
|
||||
*/
|
||||
save(options) {
|
||||
return this._super(options).then((model) => {
|
||||
if (model.get('id')) {
|
||||
this.replaceRoute('editor.edit', model);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -3,7 +3,7 @@ import Mixin from 'ember-metal/mixin';
|
||||
import PostModel from 'ghost-admin/models/post';
|
||||
import RSVP from 'rsvp';
|
||||
import boundOneWay from 'ghost-admin/utils/bound-one-way';
|
||||
import computed, {alias, mapBy, reads} from 'ember-computed';
|
||||
import computed, {mapBy, reads} from 'ember-computed';
|
||||
import ghostPaths from 'ghost-admin/utils/ghost-paths';
|
||||
import injectController from 'ember-controller/inject';
|
||||
import injectService from 'ember-service/inject';
|
||||
@ -28,6 +28,11 @@ const watchedProps = ['model.scratch', 'model.titleScratch', 'model.hasDirtyAttr
|
||||
const DEFAULT_TITLE = '(Untitled)';
|
||||
const TITLE_DEBOUNCE = testing ? 10 : 700;
|
||||
|
||||
// time in ms to save after last content edit
|
||||
const AUTOSAVE_TIMEOUT = 3000;
|
||||
// time in ms to force a save if the user is continuously typing
|
||||
const TIMEDSAVE_TIMEOUT = 60000;
|
||||
|
||||
PostModel.eachAttribute(function (name) {
|
||||
watchedProps.push(`model.${name}`);
|
||||
});
|
||||
@ -52,9 +57,6 @@ export default Mixin.create({
|
||||
editor: null,
|
||||
editorMenuIsOpen: false,
|
||||
|
||||
shouldFocusTitle: alias('model.isNew'),
|
||||
shouldFocusEditor: false,
|
||||
|
||||
navIsClosed: reads('application.autoNav'),
|
||||
|
||||
init() {
|
||||
@ -65,12 +67,12 @@ export default Mixin.create({
|
||||
},
|
||||
|
||||
_canAutosave: computed('model.{isDraft,isNew}', function () {
|
||||
return !testing && this.get('model.isDraft') && !this.get('model.isNew');
|
||||
return !testing && this.get('model.isDraft');
|
||||
}),
|
||||
|
||||
// save 3 seconds after the last edit
|
||||
_autosave: task(function* () {
|
||||
yield timeout(3000);
|
||||
yield timeout(AUTOSAVE_TIMEOUT);
|
||||
|
||||
if (this.get('_canAutosave')) {
|
||||
yield this.get('autosave').perform();
|
||||
@ -81,7 +83,7 @@ export default Mixin.create({
|
||||
_timedSave: task(function* () {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (!testing && true) {
|
||||
yield timeout(60000);
|
||||
yield timeout(TIMEDSAVE_TIMEOUT);
|
||||
|
||||
if (this.get('_canAutosave')) {
|
||||
yield this.get('autosave').perform();
|
||||
@ -170,6 +172,12 @@ export default Mixin.create({
|
||||
|
||||
this.get('model').set('statusScratch', null);
|
||||
|
||||
// redirect to edit route if saving a new record
|
||||
if (isNew && model.get('id')) {
|
||||
this.replaceRoute('editor.edit', model);
|
||||
return;
|
||||
}
|
||||
|
||||
return model;
|
||||
});
|
||||
|
||||
@ -570,6 +578,12 @@ export default Mixin.create({
|
||||
},
|
||||
|
||||
toggleLeaveEditorModal(transition) {
|
||||
// cancel autosave when showing the modal to prevent the "leave"
|
||||
// action failing due to deletion of in-flight records
|
||||
if (!this.get('showLeaveEditorModal')) {
|
||||
this.send('cancelAutosave');
|
||||
}
|
||||
|
||||
this.set('leaveEditorTransition', transition);
|
||||
this.toggleProperty('showLeaveEditorModal');
|
||||
},
|
||||
|
@ -50,9 +50,10 @@ export default Mixin.create(styleBody, ShortcutsRoute, {
|
||||
// so we abort the transition and retry after the save has completed.
|
||||
if (state.isSaving) {
|
||||
transition.abort();
|
||||
controller.get('generateSlug.last').then(() => {
|
||||
controller.get('saveTasks.last').then(() => {
|
||||
transition.retry();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
fromNewToEdit = this.get('routeName') === 'editor.new'
|
||||
|
@ -5,12 +5,6 @@ import base from 'ghost-admin/mixins/editor-base-route';
|
||||
export default AuthenticatedRoute.extend(base, {
|
||||
titleToken: 'Editor',
|
||||
|
||||
beforeModel(transition) {
|
||||
this.set('_transitionedFromNew', transition.data.fromNew);
|
||||
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
model(params) {
|
||||
/* eslint-disable camelcase */
|
||||
let query = {
|
||||
@ -42,11 +36,6 @@ export default AuthenticatedRoute.extend(base, {
|
||||
});
|
||||
},
|
||||
|
||||
setupController(controller) {
|
||||
this._super(...arguments);
|
||||
controller.set('shouldFocusEditor', this.get('_transitionedFromNew'));
|
||||
},
|
||||
|
||||
actions: {
|
||||
authorizationFailed() {
|
||||
this.get('controller').send('toggleReAuthenticateModal');
|
||||
|
@ -17,15 +17,5 @@ export default AuthenticatedRoute.extend(base, {
|
||||
controller,
|
||||
model
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
willTransition(transition) {
|
||||
// decorate the transition object so the editor.edit route
|
||||
// knows this was the previous active route
|
||||
transition.data.fromNew = true;
|
||||
|
||||
this._super(...arguments);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -35,8 +35,8 @@
|
||||
--}}
|
||||
{{#gh-markdown-editor
|
||||
tabindex="2"
|
||||
placeholder="Click here to start..."
|
||||
autofocus=shouldFocusEditor
|
||||
placeholder="Now begin writing your story..."
|
||||
autofocus=true
|
||||
uploadedImageUrls=editor.uploadedImageUrls
|
||||
mobiledoc=(readonly model.scratch)
|
||||
isFullScreen=editor.isFullScreen
|
||||
@ -54,7 +54,6 @@
|
||||
class="gh-editor-title"
|
||||
placeholder="Your Post Title"
|
||||
tabindex="1"
|
||||
shouldFocus=shouldFocusTitle
|
||||
autoExpand=".gh-markdown-editor-pane"
|
||||
focusOut=(action "saveTitle")
|
||||
update=(action (perform updateTitle))
|
||||
|
Loading…
Reference in New Issue
Block a user