mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-25 09:03:12 +03:00
Added first iteration of saving to new publish flow
refs https://github.com/TryGhost/Team/issues/1542 - extracted before/after save routines in the editor controller into separate actions - allows saving to occur in the publish flow without it needing any editor-specific knowledge - allows for easier cleanup of email related logic from the editor save tasks later on - added `saveTask` to `PublishOptions` - applies the selected options to the post model where they correspond to model attributes and keeps the previous values in memory so the changes can be undone on failure - this keeps the local model state in sync because if a publish fails we want the editor to continue showing the draft state, non-scheduled publish time, and not have an unexpected email-only state - saves the post model directly passing `adapterOptions` so the save request query params match the chosen publish options - added a `saveTask` to the `<PublishManagement>` component - passed through to the `publish-flow` modal and is triggered by the confirm button on the confirmation screen - runs the before/afterSave arguments passed in from the editor - runs the `saveTask` on `PublishOptions` which handles everything needed to change status and send emails - polls the post after saving to wait for the attached email to switch to submitted/failed which lets us show a failure message and retry button as required (message + retry not yet implemented) - adds "complete" state to publish flow once save has finished - confirms what just happened based on saved post data rather than chosen publish options - has a link to the view the post
This commit is contained in:
parent
6adecb5db1
commit
adeef741fb
@ -6,9 +6,11 @@ export default class Post extends ApplicationAdapter {
|
||||
const url = this.buildURL(modelName, id, snapshot, requestType, query);
|
||||
const parsedUrl = new URL(url);
|
||||
|
||||
if (snapshot?.adapterOptions?.sendEmailWhenPublished) {
|
||||
let emailRecipientFilter = snapshot.adapterOptions.sendEmailWhenPublished;
|
||||
// TODO: cleanup sendEmailWhenPublished when removing publishingFlow flag
|
||||
let emailRecipientFilter = snapshot?.adapterOptions?.emailRecipientFilter
|
||||
|| snapshot?.adapterOptions?.sendEmailWhenPublished;
|
||||
|
||||
if (emailRecipientFilter) {
|
||||
if (emailRecipientFilter === 'status:free,status:-free') {
|
||||
emailRecipientFilter = 'all';
|
||||
}
|
||||
|
@ -9,10 +9,15 @@
|
||||
{{#if this.isConfirming}}
|
||||
<EditorLabs::Modals::PublishFlow::Confirm
|
||||
@publishOptions={{@data.publishOptions}}
|
||||
@confirm={{noop}}
|
||||
@saveTask={{this.saveTask}}
|
||||
@cancel={{this.toggleConfirm}}
|
||||
@close={{@close}}
|
||||
/>
|
||||
{{else if this.isComplete}}
|
||||
<EditorLabs::Modals::PublishFlow::Complete
|
||||
@publishOptions={{@data.publishOptions}}
|
||||
@close={{@close}}
|
||||
/>
|
||||
{{else}}
|
||||
<EditorLabs::Modals::PublishFlow::Options
|
||||
@publishOptions={{@data.publishOptions}}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class PublishModalComponent extends Component {
|
||||
@ -10,10 +11,19 @@ export default class PublishModalComponent extends Component {
|
||||
};
|
||||
|
||||
@tracked isConfirming = false;
|
||||
@tracked isComplete = false;
|
||||
|
||||
@action
|
||||
toggleConfirm() {
|
||||
// TODO: validate?
|
||||
this.isConfirming = !this.isConfirming;
|
||||
}
|
||||
|
||||
@task
|
||||
*saveTask() {
|
||||
yield this.args.data.saveTask.perform();
|
||||
|
||||
this.isConfirming = false;
|
||||
this.isComplete = true;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
{{#let @publishOptions.post as |post|}}
|
||||
<div class="gh-publish-title">
|
||||
Your {{post.displayName}} is
|
||||
{{#if post.isScheduled}}
|
||||
scheduled
|
||||
{{else if post.emailOnly}}
|
||||
emailed
|
||||
{{else}}
|
||||
live
|
||||
{{~/if~}}
|
||||
!
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{{#if post.isScheduled}}
|
||||
{{#let (moment-site-tz post.publishedAtUTC) as |scheduledAt|}}
|
||||
On
|
||||
<strong>
|
||||
{{moment-format scheduledAt "D MMM YYYY"}}
|
||||
at
|
||||
{{moment-format scheduledAt "HH:mm"}}
|
||||
</strong>
|
||||
your
|
||||
{{/let}}
|
||||
{{else}}
|
||||
Your
|
||||
{{/if}}
|
||||
|
||||
{{post.displayName}} was
|
||||
|
||||
{{#if post.email}}
|
||||
delivered to
|
||||
|
||||
<strong>{{pluralize post.email.emailCount "member"}}</strong>
|
||||
|
||||
{{#if post.emailOnly}}
|
||||
and was <strong>not</strong>
|
||||
{{else}}
|
||||
and was
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
published on your site.
|
||||
</p>
|
||||
|
||||
<div class="gh-publish-cta">
|
||||
<button type="button" class="gh-btn gh-btn-primary gh-btn-large" {{on "click" @close}}><span>Close</span></button>
|
||||
<a href={{post.url}} class="gh-btn gh-btn-link gh-btn-large" target="_blank" rel="noopener noreferrer">Open {{post.displayName}} in new tab</a>
|
||||
</div>
|
||||
{{/let}}
|
@ -12,6 +12,7 @@
|
||||
at
|
||||
{{moment-format scheduledAt "HH:mm"}}
|
||||
</strong>
|
||||
your
|
||||
{{/let}}
|
||||
{{else}}
|
||||
Your
|
||||
@ -41,6 +42,12 @@
|
||||
</p>
|
||||
|
||||
<div class="gh-publish-cta">
|
||||
<button type="button" class="gh-btn gh-btn-green gh-btn-large" {{on "click" @confirm}}><span>{{@publishOptions.selectedPublishTypeOption.confirmButton}}</span></button>
|
||||
<GhTaskButton
|
||||
@task={{@saveTask}}
|
||||
@buttonText={{@publishOptions.selectedPublishTypeOption.confirmButton}}
|
||||
@runningText={{if @publishOptions.willOnlyEmail "Sending" "Publishing"}}
|
||||
@successText={{if @publishOptions.willOnlyEmail "Sent" "Published"}}
|
||||
@class="gh-btn gh-btn-icon gh-btn-green gh-btn-large"
|
||||
/>
|
||||
<button type="button" class="gh-btn gh-btn-link gh-btn-large" {{on "click" @cancel}}><span>Back to publish settings</span></button>
|
||||
</div>
|
@ -1,13 +1,17 @@
|
||||
import Component from '@glimmer/component';
|
||||
import EmailFailedError from 'ghost-admin/errors/email-failed-error';
|
||||
import PublishFlowModal from './modals/publish-flow';
|
||||
import PublishOptionsResource from 'ghost-admin/helpers/publish-options';
|
||||
import moment from 'moment';
|
||||
import {action, get} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
import {use} from 'ember-could-get-used-to-this';
|
||||
|
||||
const CONFIRM_EMAIL_POLL_LENGTH = 1000;
|
||||
const CONFIRM_EMAIL_MAX_POLL_LENGTH = 15 * 1000;
|
||||
|
||||
export class PublishOptions {
|
||||
// passed in services
|
||||
config = null;
|
||||
@ -18,6 +22,8 @@ export class PublishOptions {
|
||||
post = null;
|
||||
user = null;
|
||||
|
||||
@tracked totalMemberCount = 0;
|
||||
|
||||
get isLoading() {
|
||||
return this.setupTask.isRunning;
|
||||
}
|
||||
@ -30,6 +36,10 @@ export class PublishOptions {
|
||||
return this.publishType !== 'send';
|
||||
}
|
||||
|
||||
get willOnlyEmail() {
|
||||
return this.publishType === 'send';
|
||||
}
|
||||
|
||||
// publish date ------------------------------------------------------------
|
||||
|
||||
@tracked isScheduled = false;
|
||||
@ -75,10 +85,10 @@ export class PublishOptions {
|
||||
|
||||
get publishTypeOptions() {
|
||||
return [{
|
||||
value: 'publish+send',
|
||||
label: 'Publish and email',
|
||||
display: 'Publish and email',
|
||||
confirmButton: 'Publish and send',
|
||||
value: 'publish+send', // internal
|
||||
label: 'Publish and email', // shown in expanded options
|
||||
display: 'Publish and email', // shown in option title
|
||||
confirmButton: 'Publish and send', // shown in confirm step
|
||||
disabled: this.emailDisabled
|
||||
}, {
|
||||
value: 'publish',
|
||||
@ -108,13 +118,14 @@ export class PublishOptions {
|
||||
|
||||
// publish type dropdown is shown but email options are disabled
|
||||
get emailDisabled() {
|
||||
const mailgunConfigured = get(this.settings, 'mailgunIsConfigured')
|
||||
|| get(this.config, 'mailgunIsConfigured');
|
||||
const mailgunIsNotConfigured = !get(this.settings, 'mailgunIsConfigured')
|
||||
&& !get(this.config, 'mailgunIsConfigured');
|
||||
|
||||
const hasNoMembers = this.totalMemberCount === 0;
|
||||
|
||||
// TODO: check members count
|
||||
// TODO: check email limit
|
||||
|
||||
return !mailgunConfigured;
|
||||
return mailgunIsNotConfigured || hasNoMembers;
|
||||
}
|
||||
|
||||
@action
|
||||
@ -157,7 +168,7 @@ export class PublishOptions {
|
||||
this.store = store;
|
||||
this.user = user;
|
||||
|
||||
// these need to be set here rather than class-level properties because
|
||||
// this needs to be set here rather than a class-level property because
|
||||
// unlike Ember-based classes the services are not injected so can't be
|
||||
// used until after they are assigned above
|
||||
this.allNewsletters = this.store.peekAll('newsletter');
|
||||
@ -181,7 +192,9 @@ export class PublishOptions {
|
||||
@task
|
||||
*fetchRequiredDataTask() {
|
||||
// total # of members - used to enable/disable email
|
||||
const countTotalMembers = this.store.query('member', {limit: 1}).then(res => res.meta.pagination.total);
|
||||
const countTotalMembers = this.store.query('member', {limit: 1}).then((res) => {
|
||||
this.totalMemberCount = res.meta.pagination.total;
|
||||
});
|
||||
|
||||
// email limits
|
||||
// TODO: query limit service
|
||||
@ -193,10 +206,64 @@ export class PublishOptions {
|
||||
}
|
||||
|
||||
// saving ------------------------------------------------------------------
|
||||
|
||||
revertableModelProperties = ['status', 'publishedAtUTC', 'emailOnly'];
|
||||
|
||||
@task({drop: true})
|
||||
*saveTask() {
|
||||
this._applyModelChanges();
|
||||
|
||||
const adapterOptions = {};
|
||||
|
||||
if (this.willEmail) {
|
||||
adapterOptions.newsletterId = this.newsletter.id;
|
||||
// TODO: replace with real filter
|
||||
adapterOptions.emailRecipientFilter = 'status:free,status:-free';
|
||||
}
|
||||
|
||||
try {
|
||||
return yield this.post.save({adapterOptions});
|
||||
} catch (e) {
|
||||
this._revertModelChanges();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Publishing/scheduling is a side-effect of changing model properties.
|
||||
// We don't want to get into a situation where we've applied these changes
|
||||
// but they haven't been saved because that would result in confusing UI.
|
||||
//
|
||||
// Here we apply those changes from the selected publish options but keep
|
||||
// track of the previous values in case saving fails. We can't use ED's
|
||||
// rollbackAttributes() because it would also rollback any other unsaved edits
|
||||
_applyModelChanges() {
|
||||
// store backup of original values in case we need to revert
|
||||
this._originalModelValues = {};
|
||||
this.revertableModelProperties.forEach((property) => {
|
||||
this._originalModelValues[property] = this.post[property];
|
||||
});
|
||||
|
||||
this.post.status = this.isScheduled ? 'scheduled' : 'published';
|
||||
|
||||
if (this.post.isScheduled) {
|
||||
this.post.publishedAtUTC = this.scheduledAtUTC;
|
||||
}
|
||||
|
||||
this.post.emailOnly = this.publishType === 'email';
|
||||
}
|
||||
|
||||
_revertModelChanges() {
|
||||
this.revertableModelProperties.forEach((property) => {
|
||||
this.post[property] = this._originalModelValues[property];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* Component -----------------------------------------------------------------*/
|
||||
|
||||
// This component exists for the duration of the editor screen being open.
|
||||
// It's used to store the selected publish options and control the publishing flow.
|
||||
// It's used to store the selected publish options and control the
|
||||
// publishing flow modal.
|
||||
export default class PublishManagement extends Component {
|
||||
@service modals;
|
||||
|
||||
@ -218,8 +285,56 @@ export default class PublishManagement extends Component {
|
||||
this.publishOptions.resetPastScheduledAt();
|
||||
|
||||
this.publishFlowModal = this.modals.open(PublishFlowModal, {
|
||||
publishOptions: this.publishOptions
|
||||
publishOptions: this.publishOptions,
|
||||
saveTask: this.saveTask
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@task
|
||||
*saveTask() {
|
||||
// clean up blank editor cards
|
||||
// apply cloned mobiledoc
|
||||
// apply scratch values
|
||||
// generate slug if needed (should never happen - publish flow can't be opened on new posts)
|
||||
yield this.args.beforeSave();
|
||||
|
||||
// apply publish options (with undo on failure)
|
||||
// save with the required query params for emailing
|
||||
const result = yield this.publishOptions.saveTask.perform();
|
||||
|
||||
// perform any post-save cleanup for the editor
|
||||
yield this.args.afterSave(result);
|
||||
|
||||
// if emailed, wait until it has been submitted so we can show a failure message if needed
|
||||
if (this.publishOptions.post.email) {
|
||||
yield this.confirmEmailTask.perform();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@task
|
||||
*confirmEmailTask() {
|
||||
const post = this.publishOptions.post;
|
||||
|
||||
let pollTimeout = 0;
|
||||
if (post.email && post.email.status !== 'submitted') {
|
||||
while (pollTimeout < CONFIRM_EMAIL_MAX_POLL_LENGTH) {
|
||||
yield timeout(CONFIRM_EMAIL_POLL_LENGTH);
|
||||
pollTimeout += CONFIRM_EMAIL_POLL_LENGTH;
|
||||
|
||||
yield post.reload();
|
||||
|
||||
if (post.email.status === 'submitted') {
|
||||
break;
|
||||
}
|
||||
if (post.email.status === 'failed') {
|
||||
throw new EmailFailedError(post.email.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -265,7 +265,7 @@ export default class EditorController extends Controller {
|
||||
keyboardEvent?.preventDefault();
|
||||
|
||||
if (this.post.isDraft) {
|
||||
this.send('openPostPreviewModal');
|
||||
this.openPostPreviewModal();
|
||||
} else {
|
||||
window.open(this.post.previewUrl, '_blank', 'noopener');
|
||||
}
|
||||
@ -444,7 +444,8 @@ export default class EditorController extends Controller {
|
||||
/* Public tasks ----------------------------------------------------------*/
|
||||
|
||||
// separate task for autosave so that it doesn't override a manual save
|
||||
@dropTask *autosaveTask() {
|
||||
@dropTask
|
||||
*autosaveTask() {
|
||||
if (!this.get('saveTask.isRunning')) {
|
||||
return yield this.saveTask.perform({
|
||||
silent: true,
|
||||
@ -461,7 +462,7 @@ export default class EditorController extends Controller {
|
||||
let isNew = this.get('post.isNew');
|
||||
let status;
|
||||
|
||||
this.send('cancelAutosave');
|
||||
this.cancelAutosave();
|
||||
|
||||
if (options.backgroundSave && !this.hasDirtyAttributes) {
|
||||
return;
|
||||
@ -496,43 +497,11 @@ export default class EditorController extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
// ensure we remove any blank cards when performing a full save
|
||||
if (!options.backgroundSave) {
|
||||
if (this._koenig) {
|
||||
this._koenig.cleanup();
|
||||
this.set('hasDirtyAttributes', true);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the properties that are indirected
|
||||
// set mobiledoc equal to what's in the editor but create a copy so that
|
||||
// nested objects/arrays don't keep references which can mean that both
|
||||
// scratch and mobiledoc get updated simultaneously
|
||||
this.set('post.mobiledoc', JSON.parse(JSON.stringify(this.post.scratch || null)));
|
||||
// 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 a default title
|
||||
if (!this.get('post.titleScratch').trim()) {
|
||||
this.set('post.titleScratch', DEFAULT_TITLE);
|
||||
}
|
||||
|
||||
this.set('post.title', this.get('post.titleScratch'));
|
||||
this.set('post.customExcerpt', this.get('post.customExcerptScratch'));
|
||||
this.set('post.footerInjection', this.get('post.footerExcerptScratch'));
|
||||
this.set('post.headerInjection', this.get('post.headerExcerptScratch'));
|
||||
this.set('post.metaTitle', this.get('post.metaTitleScratch'));
|
||||
this.set('post.metaDescription', this.get('post.metaDescriptionScratch'));
|
||||
this.set('post.ogTitle', this.get('post.ogTitleScratch'));
|
||||
this.set('post.ogDescription', this.get('post.ogDescriptionScratch'));
|
||||
this.set('post.twitterTitle', this.get('post.twitterTitleScratch'));
|
||||
this.set('post.twitterDescription', this.get('post.twitterDescriptionScratch'));
|
||||
this.set('post.emailSubject', this.get('post.emailSubjectScratch'));
|
||||
|
||||
if (!this.get('post.slug')) {
|
||||
this.saveTitleTask.cancelAll();
|
||||
|
||||
yield this.generateSlugTask.perform();
|
||||
}
|
||||
yield this.beforeSaveTask.perform();
|
||||
|
||||
try {
|
||||
let post = yield this._savePostTask.perform(options);
|
||||
@ -577,7 +546,7 @@ export default class EditorController extends Controller {
|
||||
|
||||
// re-throw if we have a general server error
|
||||
if (error && !isInvalidError(error)) {
|
||||
this.send('error', error);
|
||||
this.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -592,6 +561,50 @@ export default class EditorController extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
@task
|
||||
*beforeSaveTask(options = {}) {
|
||||
// ensure we remove any blank cards when performing a full save
|
||||
if (!options.backgroundSave) {
|
||||
if (this._koenig) {
|
||||
this._koenig.cleanup();
|
||||
this.set('hasDirtyAttributes', true);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: There's no need for (at least) most of these scratch values.
|
||||
// Refactor so we're setting model attributes directly
|
||||
|
||||
// Set the properties that are indirected
|
||||
|
||||
// Set mobiledoc equal to what's in the editor but create a copy so that
|
||||
// nested objects/arrays don't keep references which can mean that both
|
||||
// scratch and mobiledoc get updated simultaneously
|
||||
this.set('post.mobiledoc', JSON.parse(JSON.stringify(this.post.scratch || null)));
|
||||
|
||||
// Set a default title
|
||||
if (!this.get('post.titleScratch').trim()) {
|
||||
this.set('post.titleScratch', DEFAULT_TITLE);
|
||||
}
|
||||
|
||||
this.set('post.title', this.get('post.titleScratch'));
|
||||
this.set('post.customExcerpt', this.get('post.customExcerptScratch'));
|
||||
this.set('post.footerInjection', this.get('post.footerExcerptScratch'));
|
||||
this.set('post.headerInjection', this.get('post.headerExcerptScratch'));
|
||||
this.set('post.metaTitle', this.get('post.metaTitleScratch'));
|
||||
this.set('post.metaDescription', this.get('post.metaDescriptionScratch'));
|
||||
this.set('post.ogTitle', this.get('post.ogTitleScratch'));
|
||||
this.set('post.ogDescription', this.get('post.ogDescriptionScratch'));
|
||||
this.set('post.twitterTitle', this.get('post.twitterTitleScratch'));
|
||||
this.set('post.twitterDescription', this.get('post.twitterDescriptionScratch'));
|
||||
this.set('post.emailSubject', this.get('post.emailSubjectScratch'));
|
||||
|
||||
if (!this.get('post.slug')) {
|
||||
this.saveTitleTask.cancelAll();
|
||||
|
||||
yield this.generateSlugTask.perform();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* triggered by a user manually changing slug
|
||||
*/
|
||||
@ -671,7 +684,8 @@ export default class EditorController extends Controller {
|
||||
}
|
||||
|
||||
// convenience method for saving the post and performing post-save cleanup
|
||||
@task *_savePostTask(options = {}) {
|
||||
@task
|
||||
*_savePostTask(options = {}) {
|
||||
let {post} = this;
|
||||
|
||||
const previousEmailOnlyValue = this.post.emailOnly;
|
||||
@ -695,6 +709,13 @@ export default class EditorController extends Controller {
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.afterSave(post);
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
@action
|
||||
afterSave(post) {
|
||||
this.notifications.closeAlerts('post.save');
|
||||
|
||||
// remove any unsaved tags
|
||||
@ -718,11 +739,10 @@ export default class EditorController extends Controller {
|
||||
if (titlesMatch && bodiesMatch) {
|
||||
this.set('hasDirtyAttributes', false);
|
||||
}
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
@task *saveTitleTask() {
|
||||
@task
|
||||
*saveTitleTask() {
|
||||
let post = this.post;
|
||||
let currentTitle = post.get('title');
|
||||
let newTitle = post.get('titleScratch').trim();
|
||||
@ -747,7 +767,8 @@ export default class EditorController extends Controller {
|
||||
this.ui.updateDocumentTitle();
|
||||
}
|
||||
|
||||
@enqueueTask *generateSlugTask() {
|
||||
@enqueueTask
|
||||
*generateSlugTask() {
|
||||
let title = this.get('post.titleScratch');
|
||||
|
||||
// Only set an "untitled" slug once per post
|
||||
@ -772,7 +793,8 @@ export default class EditorController extends Controller {
|
||||
}
|
||||
|
||||
// load supplementel data such as the members count in the background
|
||||
@restartableTask *backgroundLoaderTask() {
|
||||
@restartableTask
|
||||
*backgroundLoaderTask() {
|
||||
yield this.store.query('snippet', {limit: 'all'});
|
||||
}
|
||||
|
||||
@ -861,7 +883,7 @@ export default class EditorController extends Controller {
|
||||
|
||||
// if an autosave is scheduled, cancel it, save then transition
|
||||
if (this._autosaveRunning) {
|
||||
this.send('cancelAutosave');
|
||||
this.cancelAutosave();
|
||||
this.autosaveTask.cancelAll();
|
||||
|
||||
await this.autosaveTask.perform();
|
||||
@ -898,7 +920,7 @@ export default class EditorController extends Controller {
|
||||
|
||||
// make sure the save tasks aren't still running in the background
|
||||
// after leaving the edit route
|
||||
this.send('cancelAutosave');
|
||||
this.cancelAutosave();
|
||||
|
||||
if (post) {
|
||||
// clear post of any unsaved, client-generated tags
|
||||
|
@ -46,7 +46,11 @@
|
||||
data-test-contributor-save />
|
||||
{{else}}
|
||||
{{#if (feature "publishingFlow")}}
|
||||
<EditorLabs::PublishManagement @post={{this.post}} />
|
||||
<EditorLabs::PublishManagement
|
||||
@post={{this.post}}
|
||||
@beforeSave={{perform this.beforeSaveTask}}
|
||||
@afterSave={{this.afterSave}}
|
||||
/>
|
||||
{{else}}
|
||||
<GhPublishmenu
|
||||
@post={{this.post}}
|
||||
|
Loading…
Reference in New Issue
Block a user