Added initial "publishOptions" setup

refs https://github.com/TryGhost/Team/issues/1542

- adds a `PublishOptions` class
  - an instance of this class provides everything the UI needs to display and set the publish options relevant to the current post object and overall system state
  - the `publish-flow` modal is passed a `PublishOptions` instance when it opens
  - as part of the constructor it triggers a background load of any additional data it requires to control available options such as member counts, email limits, and newsletters
- adds a `{{publish-options}}` resource
  - sets up and returns `PublishOptions` instance
  - passes through service dependencies which are not available directly in the `PublishOptions` class as it's a custom native class outside of Ember's DI management
  -  used to ensure we can get a clean `PublishOptions` instance any time the passed in `post` object is replaced meaning we don't have to rely on observers and manual teardown/setup
- updated `<PublishManagement>` component
  - sets up `publishOptions` property using `@use` and the `{{publish-options}}` resource so reactivity for changing post objects is handled automatically
  - uses the `publishOptions.isLoading` property to disable the publish flow trigger button until all of the data required to manage the flow is available
- updated `publish-flow` modal to use some of the initially available `publishOptions` data
This commit is contained in:
Kevin Ansfield 2022-04-22 17:56:05 +01:00
parent bfb02d4458
commit 2be1673106
5 changed files with 209 additions and 10 deletions

View File

@ -3,6 +3,7 @@
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>

View File

@ -1,11 +1,137 @@
import Component from '@glimmer/component';
import PublishFlowModal from '../modals/editor-labs/publish-flow';
import {action} from '@ember/object';
import PublishOptionsResource from 'ghost-admin/helpers/publish-options';
import {action, get} from '@ember/object';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';
import {use} from 'ember-could-get-used-to-this';
export default class PublishFlow extends Component {
export class PublishOptions {
// passed in services
config = null;
settings = null;
store = null;
// passed in objects
post = null;
user = null;
get isLoading() {
return this.setupTask.isRunning;
}
// publish type ------------------------------------------------------------
@tracked publishType = 'publish+send';
get publishTypeOptions() {
return [{
value: 'publish+send',
display: 'published and sent',
disabled: this.emailDisabled
}, {
value: 'publish',
display: 'published'
}, {
value: 'send',
display: 'sent',
disabled: this.emailDisabled
}];
}
get selectedPublishTypeOption() {
return this.publishTypeOptions.find(pto => pto.value === this.publishType);
}
// publish type dropdown is not shown at all
get emailUnavailable() {
const emailDisabled = get(this.settings, 'editorDefaultEmailRecipients') === 'disabled'
|| get(this.settings, 'membersSignupAccess') === 'none';
return this.post.isPage || this.post.email || !this.user.canEmail || emailDisabled;
}
// publish type dropdown is shown but email options are disabled
get emailDisabled() {
const mailgunConfigured = get(this.settings, 'mailgunIsConfigured')
|| get(this.config, 'mailgunIsConfigured');
// TODO: check members count
// TODO: check email limit
return !mailgunConfigured;
}
// publish date ------------------------------------------------------------
@tracked publishDate = 'now';
// newsletter --------------------------------------------------------------
newsletters = []; // set in constructor
get defaultNewsletter() {
return this.newsletters.sort(({sortOrder: a}, {sortOrder: b}) => b - a)[0];
}
get onlyDefaultNewsletter() {
return this.newsletters.length === 1;
}
// recipients --------------------------------------------------------------
// setup -------------------------------------------------------------------
constructor({config, post, settings, store, user} = {}) {
this.config = config;
this.post = post;
this.settings = settings;
this.store = store;
this.user = user;
// these need to be set here rather than class-level properties because
// unlike Ember-based classes the services are not injected so can't be
// used until after they are assigned above
this.newsletters = this.store.peekAll('newsletter').filter(n => n.status === 'active');
this.setupTask.perform();
}
@task
*setupTask() {
yield this.fetchRequiredDataTask.perform();
// TODO: set up initial state / defaults
if (this.emailUnavailable) {
this.publishType = 'publish';
}
}
@task
*fetchRequiredDataTask() {
// total # of members - used to enable/disable email
const countTotalMembers = this.store.query('member', {limit: 1}).then(res => res.meta.pagination.total);
// email limits
// TODO: query limit service
// newsletters
const fetchNewsletters = this.store.findAll('newsletter', {reload: true});
yield Promise.all([countTotalMembers, fetchNewsletters]);
}
}
// 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.
export default class PublishManagement extends Component {
@service modals;
// ensure we get a new PublishOptions instance when @post is replaced
@use publishOptions = new PublishOptionsResource(() => [this.args.post]);
publishFlowModal = null;
willDestroy() {
@ -14,7 +140,13 @@ export default class PublishFlow extends Component {
}
@action
openPublishFlow() {
this.publishFlowModal = this.modals.open(PublishFlowModal);
openPublishFlow(event) {
event?.preventDefault();
if (!this.publishFlowModal || this.publishFlowModal.isClosing) {
this.publishFlowModal = this.modals.open(PublishFlowModal, {
publishOptions: this.publishOptions
});
}
}
}

View File

@ -9,12 +9,31 @@
<div class="gh-publish-title">Ready?</div>
<div class="gh-publish-settings">
<span class="gh-publish-setting">Right now</span>
, this post will be
<span class="gh-publish-setting">published and sent</span>
to
<span class="gh-publish-setting">all subscribers</span>
of
<span class="gh-publish-setting">The Weekly Roundup</span>.
, this {{@data.publishOptions.post.displayName}} will be
{{#if @data.publishOptions.emailUnavailable}}
published
{{else}}
<span class="gh-publish-setting">{{@data.publishOptions.selectedPublishTypeOption.display}}</span>
{{/if}}
{{#if (eq @data.publishOptions.publishType "publish")}}
on your site.
{{else}}
to
<span class="gh-publish-setting">all subscribers</span>
of
{{#if @data.publishOptions.onlyDefaultNewsletter}}
your site.
{{else}}
<span class="gh-publish-setting">The Weekly Roundup</span>.
{{/if}}
{{/if}}
</div>
<div class="gh-publish-cta">

View File

@ -0,0 +1,45 @@
import {PublishOptions} from '../components/editor-labs/publish-management';
import {Resource} from 'ember-could-get-used-to-this';
import {inject as service} from '@ember/service';
import {tracked} from '@glimmer/tracking';
export default class PublishOptionsResource extends Resource {
@service config;
@service session;
@service settings;
@service store;
@tracked publishOptions;
get value() {
return this.publishOptions;
}
setup() {
const post = this.args.positional[0];
this._post = post;
this.publishOptions = this._createPublishOptions(post);
}
update() {
// required due to a weird invalidation issue when using Ember Data with ember-could-get-used-to-this
// TODO: re-test after upgrading to ember-resources
const post = this.args.positional[0];
if (post !== this._post) {
this.publishOptions = this._createPublishOptions(post);
}
}
_createPublishOptions(post) {
const {config, settings, store} = this;
return new PublishOptions({
config,
post,
settings,
store,
user: this.session.user
});
}
}

View File

@ -54,6 +54,8 @@ export default BaseModel.extend(ValidationEngine, {
isAdmin: or('isOwnerOnly', 'isAdminOnly'),
isAuthorOrContributor: or('isAuthor', 'isContributor'),
canEmail: or('isAdmin', 'isEditor'),
isLoggedIn: computed('id', 'session.user.id', function () {
return this.id === this.get('session.user.id');
}),