mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-08 02:52:39 -05:00
Added scheduled and published states to new publish flow
closes https://github.com/TryGhost/Team/issues/1589 closes https://github.com/TryGhost/Team/issues/1590 When a post is already scheduled or published the "Publish" button changes to "Update" and when clicked shows details about the last publish / upcoming schedule along with buttons to save any changes or revert to a draft. - added separate `update-flow` modal with the save/revert options to keep the main `publish-flow` cleaner and focused just on the draft->publish flow
This commit is contained in:
parent
27cb5a9fec
commit
8fb0d6ebb2
6 changed files with 196 additions and 26 deletions
|
@ -1,5 +1,5 @@
|
|||
<div class="flex flex-column h-100 items-center overflow-auto">
|
||||
<header class="gh-publish-header" data-test-modal="publish">
|
||||
<header class="gh-publish-header" data-test-modal="publish-flow">
|
||||
<button class="gh-publish-back-button" title="Close" type="button" {{on "click" @close}}>
|
||||
<span>{{svg-jar "close-stroke"}} Close</span>
|
||||
</button>
|
||||
|
|
|
@ -26,10 +26,10 @@
|
|||
|
||||
<strong>{{pluralize post.email.emailCount "member"}}</strong>
|
||||
|
||||
and was
|
||||
|
||||
{{#if post.emailOnly}}
|
||||
and was <strong>not</strong>
|
||||
{{else}}
|
||||
and was
|
||||
<strong>not</strong>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<div class="flex flex-column h-100 items-center overflow-auto">
|
||||
<header class="gh-publish-header" data-test-modal="update-flow">
|
||||
<button class="gh-publish-back-button" title="Close" type="button" {{on "click" @close}}>
|
||||
<span>{{svg-jar "close-stroke"}} Close</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{{#let @data.publishOptions.post as |post|}}
|
||||
<div class="gh-publish-settings-container">
|
||||
<div class="gh-publish-title">
|
||||
This {{post.displayName}} is
|
||||
<span>{{post.status}}</span>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{{#let (moment-site-tz post.publishedAtUTC) as |publishedAt|}}
|
||||
On
|
||||
<strong>
|
||||
{{moment-format publishedAt "D MMM YYYY"}}
|
||||
at
|
||||
{{moment-format publishedAt "HH:mm"}}
|
||||
</strong>
|
||||
your
|
||||
{{/let}}
|
||||
|
||||
{{post.displayName}}
|
||||
|
||||
{{if post.isScheduled "will be" "was"}}
|
||||
|
||||
{{#if (or post.isPublished (and post.isScheduled post.emailRecipientFilter (not post.email)))}}
|
||||
{{#if post.emailOnly}}
|
||||
sent to
|
||||
{{else}}
|
||||
published and sent to
|
||||
{{/if}}
|
||||
|
||||
<strong>{{pluralize post.email.emailCount "member"}}</strong>.
|
||||
{{else}}
|
||||
published.
|
||||
{{/if}}
|
||||
</p>
|
||||
|
||||
{{#if (and post.isScheduled post.email)}}
|
||||
<p>
|
||||
This post was previously emailed to
|
||||
<strong>{{pluralize post.email.emailCount "member"}}</strong> on
|
||||
|
||||
{{#let (moment-site-tz post.email.createdAtUTC) as |sentAt|}}
|
||||
<strong>
|
||||
{{moment-format sentAt "D MMM YYYY"}}
|
||||
at
|
||||
{{moment-format sentAt "HH:mm"}}.
|
||||
</strong>
|
||||
{{/let}}
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
<div class="gh-publish-cta">
|
||||
<GhTaskButton
|
||||
@task={{this.saveTask}}
|
||||
@buttonText="Save changes"
|
||||
@runningText="Saving"
|
||||
@successText="Saved"
|
||||
@class="gh-btn gh-btn-icon gh-btn-primary gh-btn-large mr4"
|
||||
/>
|
||||
|
||||
<GhTaskButton
|
||||
@task={{this.revertToDraftTask}}
|
||||
@buttonText="Revert to draft"
|
||||
@runningText="Reverting"
|
||||
@successText="Reverted"
|
||||
@class="gh-btn gh-btn-icon gh-btn-secondary gh-btn-large"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{/let}}
|
||||
</div>
|
24
ghost/admin/app/components/editor-labs/modals/update-flow.js
Normal file
24
ghost/admin/app/components/editor-labs/modals/update-flow.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import Component from '@glimmer/component';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export default class UpdateFlowModalComponent extends Component {
|
||||
static modalOptions = {
|
||||
className: 'fullscreen-modal-total-overlay',
|
||||
omitBackdrop: true,
|
||||
ignoreBackdropClick: true
|
||||
};
|
||||
|
||||
@task
|
||||
*saveTask() {
|
||||
yield this.args.data.saveTask.perform();
|
||||
this.args.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
@task
|
||||
*revertToDraftTask() {
|
||||
yield this.args.data.revertToDraftTask.perform();
|
||||
this.args.close();
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,21 @@
|
|||
<button
|
||||
type="button"
|
||||
class="gh-btn gh-btn-editor darkgrey gh-publishmenu-trigger"
|
||||
{{on "click" this.openPublishFlow}}
|
||||
{{on-key "cmd+shift+p"}}
|
||||
disabled={{this.publishOptions.isLoading}}
|
||||
data-test-button="publish-flow"
|
||||
>
|
||||
<span>Publish</span>
|
||||
</button>
|
||||
{{#if @post.isDraft}}
|
||||
<button
|
||||
type="button"
|
||||
class="gh-btn gh-btn-editor darkgrey gh-publishmenu-trigger"
|
||||
{{on "click" this.openPublishFlow}}
|
||||
{{on-key "cmd+shift+p"}}
|
||||
disabled={{this.publishOptions.isLoading}}
|
||||
data-test-button="publish-flow"
|
||||
>
|
||||
<span>Publish</span>
|
||||
</button>
|
||||
{{else}}
|
||||
<button
|
||||
type="button"
|
||||
class="gh-btn gh-btn-editor darkgrey gh-publishmenu-trigger"
|
||||
{{on "click" this.openUpdateFlow}}
|
||||
data-test-button="update-flow"
|
||||
>
|
||||
<span>Update</span>
|
||||
</button>
|
||||
{{/if}}
|
|
@ -2,6 +2,7 @@ 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 UpdateFlowModal from './modals/update-flow';
|
||||
import moment from 'moment';
|
||||
import {action, get} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
@ -29,7 +30,10 @@ export class PublishOptions {
|
|||
}
|
||||
|
||||
get willEmail() {
|
||||
return this.publishType !== 'publish' && this.recipientFilter;
|
||||
return this.publishType !== 'publish'
|
||||
&& this.recipientFilter
|
||||
&& this.post.isDraft
|
||||
&& !this.post.email;
|
||||
}
|
||||
|
||||
get willPublish() {
|
||||
|
@ -225,8 +229,6 @@ export class PublishOptions {
|
|||
|
||||
// saving ------------------------------------------------------------------
|
||||
|
||||
revertableModelProperties = ['status', 'publishedAtUTC', 'emailOnly'];
|
||||
|
||||
@task({drop: true})
|
||||
*saveTask() {
|
||||
this._applyModelChanges();
|
||||
|
@ -246,6 +248,26 @@ export class PublishOptions {
|
|||
}
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*revertToDraftTask() {
|
||||
const originalStatus = this.post.status;
|
||||
const originalPublishedAtUTC = this.post.publishedAtUTC;
|
||||
|
||||
try {
|
||||
if (this.post.isScheduled) {
|
||||
this.post.publishedAtUTC = null;
|
||||
}
|
||||
|
||||
this.post.status = 'draft';
|
||||
|
||||
return yield this.post.save();
|
||||
} catch (e) {
|
||||
this.post.status = originalStatus;
|
||||
this.post.publishedAtUTC = originalPublishedAtUTC;
|
||||
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.
|
||||
|
@ -256,21 +278,31 @@ export class PublishOptions {
|
|||
_applyModelChanges() {
|
||||
// store backup of original values in case we need to revert
|
||||
this._originalModelValues = {};
|
||||
this.revertableModelProperties.forEach((property) => {
|
||||
|
||||
// this only applies to the full publish flow which is only available for drafts
|
||||
if (!this.post.isDraft) {
|
||||
return;
|
||||
}
|
||||
|
||||
const revertableModelProperties = ['status', 'publishedAtUTC', 'emailOnly'];
|
||||
|
||||
revertableModelProperties.forEach((property) => {
|
||||
this._originalModelValues[property] = this.post[property];
|
||||
});
|
||||
|
||||
this.post.status = this.isScheduled ? 'scheduled' : 'published';
|
||||
|
||||
if (this.post.isScheduled) {
|
||||
if (this.isScheduled) {
|
||||
this.post.publishedAtUTC = this.scheduledAtUTC;
|
||||
}
|
||||
|
||||
this.post.emailOnly = this.publishType === 'email';
|
||||
if (this.willEmail) {
|
||||
this.post.emailOnly = this.publishType === 'email';
|
||||
}
|
||||
}
|
||||
|
||||
_revertModelChanges() {
|
||||
this.revertableModelProperties.forEach((property) => {
|
||||
Object.keys(this._originalModelValues).forEach((property) => {
|
||||
this.post[property] = this._originalModelValues[property];
|
||||
});
|
||||
}
|
||||
|
@ -279,8 +311,9 @@ export class PublishOptions {
|
|||
/* 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 modal.
|
||||
// It's used to store the selected publish options, control the publishing flow
|
||||
// modal display, and provide an editor-specific save behaviour wrapper around
|
||||
// PublishOptions saving.
|
||||
export default class PublishManagement extends Component {
|
||||
@service modals;
|
||||
|
||||
|
@ -288,6 +321,7 @@ export default class PublishManagement extends Component {
|
|||
@use publishOptions = new PublishOptionsResource(() => [this.args.post]);
|
||||
|
||||
publishFlowModal = null;
|
||||
updateFlowModal = null;
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
|
@ -298,6 +332,8 @@ export default class PublishManagement extends Component {
|
|||
openPublishFlow(event) {
|
||||
event?.preventDefault();
|
||||
|
||||
this.updateFlowModal?.close();
|
||||
|
||||
if (!this.publishFlowModal || this.publishFlowModal.isClosing) {
|
||||
this.publishOptions.resetPastScheduledAt();
|
||||
|
||||
|
@ -308,8 +344,25 @@ export default class PublishManagement extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
@action
|
||||
openUpdateFlow(event) {
|
||||
event?.preventDefault();
|
||||
|
||||
this.publishFlowModal?.close();
|
||||
|
||||
if (!this.updateFlowModal || this.updateFlowModal.isClosing) {
|
||||
this.updateFlowModal = this.modals.open(UpdateFlowModal, {
|
||||
publishOptions: this.publishOptions,
|
||||
saveTask: this.saveTask,
|
||||
revertToDraftTask: this.revertToDraftTask
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@task
|
||||
*saveTask() {
|
||||
*saveTask({taskName = 'saveTask'} = {}) {
|
||||
const willEmail = this.publishOptions.willEmail;
|
||||
|
||||
// clean up blank editor cards
|
||||
// apply cloned mobiledoc
|
||||
// apply scratch values
|
||||
|
@ -318,13 +371,13 @@ export default class PublishManagement extends Component {
|
|||
|
||||
// apply publish options (with undo on failure)
|
||||
// save with the required query params for emailing
|
||||
const result = yield this.publishOptions.saveTask.perform();
|
||||
const result = yield this.publishOptions[taskName].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) {
|
||||
if (willEmail && this.publishOptions.post.email) {
|
||||
yield this.confirmEmailTask.perform();
|
||||
}
|
||||
|
||||
|
@ -354,4 +407,9 @@ export default class PublishManagement extends Component {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
@task
|
||||
*revertToDraftTask() {
|
||||
return yield this.saveTask.perform({taskName: 'revertToDraftTask'});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue