0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-17 23:44:39 -05:00

Added newsletter dropdown to publish menu

closes https://github.com/TryGhost/Team/issues/1479

- updated post adapter to append `?newsletter_id=xyz` when passed a `newsletterId` adapterOption
- updated editor save task to pass `options.newsletterId` through as `adapterOptions.newsletterId`
- set up `post.newsletter` relationship ready for handling embedded newsletter association from the API
  - explicitly deleted when serializing back to the API as it doesn't yet ignore the attribute
- updated `<GhPublishmenu>` for newsletter support
  - fetches newsletters on first render so they are available in the dropdown
  - sets "default" (first in the ordered list) newsletter as the initially selected newsletter
  - adds newsletter dropdown to draft publish menu
  - passes `newsletterId` option to editor save task when it's set

This is a minimal implementation for testing. Not included:
- correct free/paid member counts based on selected newsletter
- correct member count in confirmation modal
- indication of selected newsletter for scheduled post
This commit is contained in:
Kevin Ansfield 2022-04-06 10:22:00 +01:00
parent 114b359ab7
commit e5c26aac89
9 changed files with 79 additions and 13 deletions

View file

@ -1,14 +1,12 @@
import ApplicationAdapter from 'ghost-admin/adapters/application';
import classic from 'ember-classic-decorator';
@classic
export default class Post extends ApplicationAdapter {
// posts and pages now include everything by default
buildIncludeURL(store, modelName, id, snapshot, requestType, query) {
let url = this.buildURL(modelName, id, snapshot, requestType, query);
let parsedUrl = new URL(url);
const url = this.buildURL(modelName, id, snapshot, requestType, query);
const parsedUrl = new URL(url);
if (snapshot && snapshot.adapterOptions && snapshot.adapterOptions.sendEmailWhenPublished) {
if (snapshot?.adapterOptions?.sendEmailWhenPublished) {
let emailRecipientFilter = snapshot.adapterOptions.sendEmailWhenPublished;
if (emailRecipientFilter === 'status:free,status:-free') {
@ -18,6 +16,11 @@ export default class Post extends ApplicationAdapter {
parsedUrl.searchParams.append('email_recipient_filter', emailRecipientFilter);
}
if (snapshot?.adapterOptions?.newsletterId) {
const newsletterId = snapshot.adapterOptions.newsletterId;
parsedUrl.searchParams.append('newsletter_id', newsletterId);
}
return parsedUrl.toString();
}

View file

@ -61,6 +61,22 @@
</p>
{{else}}
<div class="form-group">
{{#if (and (feature "multipleNewsletters") (gt @availableNewsletters.length 1))}}
<div class="mb3">
<p>Newsletter</p>
<PowerSelect
@selected={{@selectedNewsletter}}
@options={{@availableNewsletters}}
@onChange={{@selectNewsletter}}
@triggerComponent="gh-power-select/trigger"
@renderInPlace={{true}}
as |newsletter|
>
{{newsletter.name}}
</PowerSelect>
</div>
{{/if}}
<GhMembersRecipientSelect
@filter={{@recipientsFilter}}
@onChange={{@setSendEmailWhenPublished}}

View file

@ -42,6 +42,9 @@
@sendingEmailLimitError={{this.sendingEmailLimitError}}
@distributionAction={{this.distributionAction}}
@setDistributionAction={{action "setDistributionAction"}}
@availableNewsletters={{this.availableNewsletters}}
@selectedNewsletter={{this.selectedNewsletter}}
@selectNewsletter={{this.selectNewsletter}}
data-test-publishmenu-draft="true" />
{{/if}}

View file

@ -1,8 +1,8 @@
import Component from '@ember/component';
import ConfirmPublishModal from './modals/editor/confirm-publish';
import EmailFailedError from 'ghost-admin/errors/email-failed-error';
import {action, computed} from '@ember/object';
import {bind, schedule} from '@ember/runloop';
import {computed} from '@ember/object';
import {or, reads} from '@ember/object/computed';
import {inject as service} from '@ember/service';
import {task, timeout} from 'ember-concurrency';
@ -32,6 +32,7 @@ export default Component.extend({
typedDateError: null,
isSendingEmailLimited: false,
sendingEmailLimitError: '',
selectedNewsletter: null,
_publishedAtBlogTZ: null,
_previousStatus: null,
@ -226,6 +227,11 @@ export default Component.extend({
}
},
didInsertElement() {
this._super(...arguments);
this.fetchNewslettersTask.perform();
},
actions: {
setSaveType(saveType) {
let post = this.post;
@ -313,6 +319,10 @@ export default Component.extend({
}
},
get availableNewsletters() {
return this.store.peekAll('newsletter').filter(n => n.status === 'active');
},
updateSaveTypeForPostStatus(status) {
if (status === 'draft' || status === 'published') {
this.set('saveType', 'publish');
@ -395,6 +405,7 @@ export default Component.extend({
post: this.post,
emailOnly: this.emailOnly,
sendEmailWhenPublished: this.sendEmailWhenPublished,
newsletterId: this.newsletterId,
isScheduled: saveType === 'schedule',
confirm: this.saveWithConfirmedPublish.perform,
retryEmailSend: this.retryEmailSendTask.perform
@ -437,6 +448,24 @@ export default Component.extend({
return email;
}),
selectNewsletter: action(function (newsletter) {
this.set('selectedNewsletter', newsletter);
}),
fetchNewslettersTask: task(function* () {
if (this.feature.multipleNewsletters) {
const newsletters = yield this.store.query('newsletter', {
filter: 'status:active',
order: 'sort_order ASC'
});
const defaultNewsletter = newsletters.toArray()[0];
this.defaultNewsletter = defaultNewsletter;
this.set('selectedNewsletter', defaultNewsletter);
}
}),
_saveTask: task(function* () {
let {
post,
@ -453,7 +482,7 @@ export default Component.extend({
try {
// will show alert for non-date related failed validations
post = yield this.saveTask.perform({sendEmailWhenPublished, emailOnly});
post = yield this.saveTask.perform({sendEmailWhenPublished, newsletterId: this.selectedNewsletter?.id, emailOnly});
this._cachePublishedAtBlogTZ();
@ -492,6 +521,8 @@ export default Component.extend({
},
_cleanup() {
this.set('selectedNewsletter', this.defaultNewsletter);
if (this.post.isScheduled && this.post.emailOnly) {
this.set('distributionAction', 'send');
} else if (this.post.isPage || !this.defaultEmailRecipients) {

View file

@ -490,7 +490,10 @@ export default class EditorController extends Controller {
let isPublishing = status === 'published' && !this.post.isPublished;
let isScheduling = status === 'scheduled' && !this.post.isScheduled;
if (options.sendEmailWhenPublished && (isPublishing || isScheduling)) {
options.adapterOptions = Object.assign({}, options.adapterOptions, {sendEmailWhenPublished: options.sendEmailWhenPublished});
options.adapterOptions = Object.assign({}, options.adapterOptions, {
sendEmailWhenPublished: options.sendEmailWhenPublished,
newsletterId: options.newsletterId
});
}
}

View file

@ -114,6 +114,7 @@ export default Model.extend(Comparable, ValidationEngine, {
authors: hasMany('user', {embedded: 'always', async: false}),
createdBy: belongsTo('user', {async: true}),
email: belongsTo('email', {async: false}),
newsletter: belongsTo('newsletter', {embedded: 'always', async: false}),
publishedBy: belongsTo('user', {async: true}),
tags: hasMany('tag', {embedded: 'always', async: false}),

View file

@ -10,7 +10,8 @@ export default class PostSerializer extends ApplicationSerializer.extend(Embedde
publishedAtUTC: {key: 'published_at'},
createdAtUTC: {key: 'created_at'},
updatedAtUTC: {key: 'updated_at'},
email: {embedded: 'always'}
email: {embedded: 'always'},
newsletter: {embedded: 'always'}
};
serialize(/*snapshot, options*/) {
@ -23,6 +24,7 @@ export default class PostSerializer extends ApplicationSerializer.extend(Embedde
delete json.url;
delete json.send_email_when_published;
delete json.email_recipient_filter;
delete json.newsletter;
// Deprecated property (replaced with data.authors)
delete json.author;

View file

@ -71,15 +71,21 @@ export default function mockPosts(server) {
});
});
server.put('/posts/:id/', function ({posts, users, tags}, {params}) {
let attrs = this.normalizedRequestAttrs();
let post = posts.find(params.id);
server.put('/posts/:id/', function ({newsletters, posts, users, tags}, {params}) {
const attrs = this.normalizedRequestAttrs();
const post = posts.find(params.id);
attrs.authors = extractAuthors(attrs, users);
attrs.tags = extractTags(attrs, tags);
attrs.updatedAt = moment.utc().toDate();
if (params.newsletter_id) {
const newsletter = newsletters.find(params.newsletter_id);
post.newsletter = newsletter;
post.save();
}
return post.update(attrs);
});

View file

@ -3,5 +3,6 @@ import {Model, belongsTo, hasMany} from 'miragejs';
export default Model.extend({
tags: hasMany(),
authors: hasMany('user'),
email: belongsTo()
email: belongsTo(),
newsletter: belongsTo()
});