mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Fixed potential unexpected unpublish and infinite loop when leaving editor (#18816)
closes https://github.com/TryGhost/Product/issues/4059 - modified `saveTask` so when it has the `leavingEditor` option it doesn't attempt to set a new post status - save when leaving the editor is never a publish/unpublish-related event, rather it's a convenience autosave and a forced revision creation so modifying the post status should never happen for those saves - updated the `willTransition` handling to avoid repeated saves - sets a property on the controller whenever we attempt a save+transition retry on leaving the editor then skips any further attempted saves on transitions - although it would be preferable we can't use `try/catch` on these saves because our save task always catches and doesn't re-throw, adjusting that would be a much larger change than we want to make for this fix
This commit is contained in:
parent
b9c2989325
commit
541a1fae5e
1 changed files with 46 additions and 29 deletions
|
@ -131,6 +131,7 @@ export default class LexicalEditorController extends Controller {
|
|||
/* private properties ----------------------------------------------------*/
|
||||
|
||||
_leaveConfirmed = false;
|
||||
_saveOnLeavePerformed = false;
|
||||
_previousTagNames = null; // set by setPost and _postSaved, used in hasDirtyAttributes
|
||||
|
||||
/* computed properties ---------------------------------------------------*/
|
||||
|
@ -433,7 +434,6 @@ export default class LexicalEditorController extends Controller {
|
|||
*saveTask(options = {}) {
|
||||
let prevStatus = this.get('post.status');
|
||||
let isNew = this.get('post.isNew');
|
||||
let status;
|
||||
const adapterOptions = {};
|
||||
|
||||
this.cancelAutosave();
|
||||
|
@ -442,28 +442,39 @@ export default class LexicalEditorController extends Controller {
|
|||
return;
|
||||
}
|
||||
|
||||
if (options.backgroundSave) {
|
||||
// do not allow a post's status to be set to published by a background save
|
||||
status = 'draft';
|
||||
// leaving the editor should never result in a status change, we only save on editor
|
||||
// leave to trigger a revision save
|
||||
if (options.leavingEditor) {
|
||||
// ensure we always have a status present otherwise we'll error when saving
|
||||
if (!this.post.status) {
|
||||
this.post.status = 'draft';
|
||||
}
|
||||
} else {
|
||||
if (this.get('post.pastScheduledTime')) {
|
||||
status = (!this.willSchedule && !this.willPublish) ? 'draft' : 'published';
|
||||
let status;
|
||||
|
||||
if (options.backgroundSave) {
|
||||
// do not allow a post's status to be set to published by a background save
|
||||
status = 'draft';
|
||||
} else {
|
||||
if (this.willPublish && !this.get('post.isScheduled')) {
|
||||
status = 'published';
|
||||
} else if (this.willSchedule && !this.get('post.isPublished')) {
|
||||
status = 'scheduled';
|
||||
} else if (this.get('post.isSent')) {
|
||||
status = 'sent';
|
||||
if (this.get('post.pastScheduledTime')) {
|
||||
status = (!this.willSchedule && !this.willPublish) ? 'draft' : 'published';
|
||||
} else {
|
||||
status = 'draft';
|
||||
if (this.willPublish && !this.get('post.isScheduled')) {
|
||||
status = 'published';
|
||||
} else if (this.willSchedule && !this.get('post.isPublished')) {
|
||||
status = 'scheduled';
|
||||
} else if (this.get('post.isSent')) {
|
||||
status = 'sent';
|
||||
} else {
|
||||
status = 'draft';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set manually here instead of in beforeSaveTask because the
|
||||
// new publishing flow sets the post status manually on publish
|
||||
this.set('post.status', status);
|
||||
// set manually here instead of in beforeSaveTask because the
|
||||
// new publishing flow sets the post status manually on publish
|
||||
this.set('post.status', status);
|
||||
}
|
||||
|
||||
const explicitSave = !options.backgroundSave;
|
||||
const leavingEditor = options.leavingEditor;
|
||||
|
@ -496,8 +507,7 @@ export default class LexicalEditorController extends Controller {
|
|||
yield this.modals.open(ReAuthenticateModal);
|
||||
|
||||
if (this.session.isAuthenticated) {
|
||||
this.saveTask.perform(options);
|
||||
return;
|
||||
return this.saveTask.perform(options);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -959,6 +969,10 @@ export default class LexicalEditorController extends Controller {
|
|||
return transition.retry();
|
||||
}
|
||||
|
||||
const fromNewToEdit = this.router.currentRouteName === 'lexical-editor.new'
|
||||
&& transition.targetName === 'lexical-editor.edit'
|
||||
&& transition.intent.contexts?.[0]?.id === post.id;
|
||||
|
||||
// clean up blank cards when leaving the editor if we have a draft post
|
||||
// - blank cards could be left around due to autosave triggering whilst
|
||||
// a blank card is present then the user attempting to leave
|
||||
|
@ -968,31 +982,30 @@ export default class LexicalEditorController extends Controller {
|
|||
// this._koenig.cleanup();
|
||||
// }
|
||||
|
||||
// if we need to save when leaving the editor, abort the transition, save,
|
||||
// then retry. If a previous transition already performed a save, skip to
|
||||
// avoid potential infinite transition+save loops
|
||||
|
||||
let hasDirtyAttributes = this.hasDirtyAttributes;
|
||||
let state = post.getProperties('isDeleted', 'isSaving', 'hasDirtyAttributes', 'isNew');
|
||||
|
||||
// Check if anything has changed since the last revision
|
||||
let postRevisions = post.get('postRevisions').toArray();
|
||||
let latestRevision = postRevisions[postRevisions.length - 1];
|
||||
let hasChangedSinceLastRevision = !post.isNew && post.lexical.replaceAll(this.config.blogUrl, '') !== latestRevision.lexical.replaceAll(this.config.blogUrl, '');
|
||||
|
||||
let fromNewToEdit = this.router.currentRouteName === 'lexical-editor.new'
|
||||
&& transition.targetName === 'lexical-editor.edit'
|
||||
&& transition.intent.contexts
|
||||
&& transition.intent.contexts[0]
|
||||
&& transition.intent.contexts[0].id === post.id;
|
||||
let hasChangedSinceLastRevision = !latestRevision || (!post.isNew && post.lexical.replaceAll(this.config.blogUrl, '') !== latestRevision.lexical.replaceAll(this.config.blogUrl, ''));
|
||||
|
||||
let deletedWithoutChanges = state.isDeleted
|
||||
&& (state.isSaving || !state.hasDirtyAttributes);
|
||||
&& (state.isSaving || !state.hasDirtyAttributes);
|
||||
|
||||
// If leaving the editor and the post has changed since we last saved a revision, always save a new revision
|
||||
if (hasChangedSinceLastRevision) {
|
||||
if (!this._saveOnLeavePerformed && hasChangedSinceLastRevision) {
|
||||
transition.abort();
|
||||
if (this._autosaveRunning) {
|
||||
this.cancelAutosave();
|
||||
this.autosaveTask.cancelAll();
|
||||
}
|
||||
await this.autosaveTask.perform({leavingEditor: true, backgroundSave: false});
|
||||
this._saveOnLeavePerformed = true;
|
||||
return transition.retry();
|
||||
}
|
||||
|
||||
|
@ -1013,7 +1026,10 @@ export default class LexicalEditorController extends Controller {
|
|||
this.autosaveTask.cancelAll();
|
||||
|
||||
// If leaving the editor, always save a revision
|
||||
await this.autosaveTask.perform({leavingEditor: true});
|
||||
if (!this._saveOnLeavePerformed) {
|
||||
await this.autosaveTask.perform({leavingEditor: true});
|
||||
this._saveOnLeavePerformed = true;
|
||||
}
|
||||
return transition.retry();
|
||||
}
|
||||
|
||||
|
@ -1063,6 +1079,7 @@ export default class LexicalEditorController extends Controller {
|
|||
|
||||
this._previousTagNames = [];
|
||||
this._leaveConfirmed = false;
|
||||
this._saveOnLeavePerformed = false;
|
||||
|
||||
this.set('post', null);
|
||||
this.set('hasDirtyAttributes', false);
|
||||
|
|
Loading…
Add table
Reference in a new issue