diff --git a/ghost/admin/app/components/modals/edit-newsletter.hbs b/ghost/admin/app/components/modals/edit-newsletter.hbs new file mode 100644 index 0000000000..2053db99b3 --- /dev/null +++ b/ghost/admin/app/components/modals/edit-newsletter.hbs @@ -0,0 +1,43 @@ + \ No newline at end of file diff --git a/ghost/admin/app/components/modals/edit-newsletter.js b/ghost/admin/app/components/modals/edit-newsletter.js new file mode 100644 index 0000000000..0a058ad24e --- /dev/null +++ b/ghost/admin/app/components/modals/edit-newsletter.js @@ -0,0 +1,48 @@ +import Component from '@glimmer/component'; +import {action} from '@ember/object'; +import {task} from 'ember-concurrency'; +import {tracked} from '@glimmer/tracking'; + +export default class EditNewsletterModal extends Component { + static modalOptions = { + className: 'fullscreen-modal-full-overlay fullscreen-modal-portal-settings' + }; + + @tracked tab = 'settings'; + + willDestroy() { + super.willDestroy(...arguments); + this.args.data.newsletter.rollbackAttributes(); + } + + @action + changeTab(tab) { + this.tab = tab; + } + + @action + saveViaKeyboard(event, responder) { + responder.stopPropagation(); + event.preventDefault(); + + this.saveTask.perform(); + } + + @task + *saveTask() { + try { + const result = yield this.args.data.newsletter.save(); + + this.args.data.afterSave?.(result); + + return result; + } catch (e) { + if (e === undefined) { + // validation error + return false; + } + + throw e; + } + } +} diff --git a/ghost/admin/app/components/modals/edit-newsletter/design.hbs b/ghost/admin/app/components/modals/edit-newsletter/design.hbs new file mode 100644 index 0000000000..f6778bdf42 --- /dev/null +++ b/ghost/admin/app/components/modals/edit-newsletter/design.hbs @@ -0,0 +1,166 @@ +
+ +
+ \ No newline at end of file diff --git a/ghost/admin/app/components/modals/edit-newsletter/design.js b/ghost/admin/app/components/modals/edit-newsletter/design.js new file mode 100644 index 0000000000..1efa24c0d0 --- /dev/null +++ b/ghost/admin/app/components/modals/edit-newsletter/design.js @@ -0,0 +1,27 @@ +import Component from '@glimmer/component'; +import {IMAGE_EXTENSIONS} from 'ghost-admin/components/gh-image-uploader'; +import {action} from '@ember/object'; +import {inject as service} from '@ember/service'; + +export default class EditNewsletterDesignForm extends Component { + @service settings; + + imageExtensions = IMAGE_EXTENSIONS; + + @action + imageUploaded(property, images) { + if (images[0]) { + this.args.newsletter[property] = images[0].url; + } + } + + @action + changeSetting(property, value) { + this.args.newsletter[property] = value; + } + + @action + toggleSetting(property, event) { + this.args.newsletter[property] = event.target.checked; + } +} diff --git a/ghost/admin/app/components/modals/edit-newsletter/preview.hbs b/ghost/admin/app/components/modals/edit-newsletter/preview.hbs new file mode 100644 index 0000000000..704210c06c --- /dev/null +++ b/ghost/admin/app/components/modals/edit-newsletter/preview.hbs @@ -0,0 +1,55 @@ + \ No newline at end of file diff --git a/ghost/admin/app/components/modals/edit-newsletter/preview.js b/ghost/admin/app/components/modals/edit-newsletter/preview.js new file mode 100644 index 0000000000..255f04bf6b --- /dev/null +++ b/ghost/admin/app/components/modals/edit-newsletter/preview.js @@ -0,0 +1,26 @@ +import Component from '@glimmer/component'; +import {htmlSafe} from '@ember/template'; +import {inject as service} from '@ember/service'; + +export default class EditNewsletterPreview extends Component { + @service config; + @service ghostPaths; + @service session; + @service settings; + + get showHeader() { + return (this.args.newsletter.showHeaderIcon && this.settings.get('icon')) + || this.args.newsletter.showHeaderTitle; + } + + get featureImageUrl() { + // keep path separate so asset rewriting correctly picks it up + const imagePath = '/img/user-cover.png'; + const fullPath = this.ghostPaths.assetRoot.replace(/\/$/, '') + imagePath; + return fullPath; + } + + get featureImageStyle() { + return htmlSafe(`background-image: url(${this.featureImageUrl})`); + } +} diff --git a/ghost/admin/app/components/modals/edit-newsletter/settings.hbs b/ghost/admin/app/components/modals/edit-newsletter/settings.hbs new file mode 100644 index 0000000000..a0812e9790 --- /dev/null +++ b/ghost/admin/app/components/modals/edit-newsletter/settings.hbs @@ -0,0 +1,86 @@ +
+ +
\ No newline at end of file diff --git a/ghost/admin/app/components/modals/edit-newsletter/settings.js b/ghost/admin/app/components/modals/edit-newsletter/settings.js new file mode 100644 index 0000000000..fa46536ca2 --- /dev/null +++ b/ghost/admin/app/components/modals/edit-newsletter/settings.js @@ -0,0 +1,23 @@ +import Component from '@glimmer/component'; +import {action} from '@ember/object'; +import {inject as service} from '@ember/service'; + +export default class EditNewsletterSettingsForm extends Component { + @service config; + @service settings; + + @action + onChange(property, event) { + this.args.newsletter[property] = event.target.checked; + } + + @action + toggleProperty(property) { + this.args.newsletter[property] = !this.args.newsletter[property]; + } + + @action + onInput(property, event) { + this.args.newsletter[property] = event.target.value; + } +} diff --git a/ghost/admin/app/components/settings/members-email-labs.hbs b/ghost/admin/app/components/settings/members-email-labs.hbs index f7875f9680..cfacfe11b2 100644 --- a/ghost/admin/app/components/settings/members-email-labs.hbs +++ b/ghost/admin/app/components/settings/members-email-labs.hbs @@ -25,7 +25,7 @@ {{#if this.emailNewsletterEnabled}} - +

General settings

diff --git a/ghost/admin/app/components/settings/members-email-labs.js b/ghost/admin/app/components/settings/members-email-labs.js index d5155efe80..eb273a3f93 100644 --- a/ghost/admin/app/components/settings/members-email-labs.js +++ b/ghost/admin/app/components/settings/members-email-labs.js @@ -20,7 +20,6 @@ export default class MembersEmailLabs extends Component { @tracked recipientsSelectValue = this._getDerivedRecipientsSelectValue(); @tracked showFromAddressConfirmation = false; - @tracked showEmailDesignSettings = false; mailgunRegions = [US, EU]; @@ -78,11 +77,6 @@ export default class MembersEmailLabs extends Component { this.showFromAddressConfirmation = !this.showFromAddressConfirmation; } - @action - closeEmailDesignSettings() { - this.showEmailDesignSettings = false; - } - @action setMailgunDomain(event) { this.settings.set('mailgunDomain', event.target.value); diff --git a/ghost/admin/app/components/settings/members-email-labs/newsletter-management.hbs b/ghost/admin/app/components/settings/members-email-labs/newsletter-management.hbs index 0c6c73fbc8..d90cdf3b92 100644 --- a/ghost/admin/app/components/settings/members-email-labs/newsletter-management.hbs +++ b/ghost/admin/app/components/settings/members-email-labs/newsletter-management.hbs @@ -25,8 +25,10 @@
- {{#each this.newsletters.newsletters as |newsletter|}} - {{#if (eq newsletter.status "active")}} + {{#if this.loadNewslettersTask.isRunning}} +
... loading
+ {{else}} + {{#each this.activeNewsletters as |newsletter|}}
{{svg-jar "grab" class="grab-newsletter"}}
@@ -65,25 +67,25 @@ @classNames="gh-newsletter-actions-menu dropdown-menu dropdown-triangle-top-right" >
  • - + Edit
  • -
  • {{else}} - + Customize → {{/if}}
    - {{/if}} - {{/each}} + {{else}} +
    No newsletters found
    + {{/each}} + {{/if}}
    - + {{svg-jar "add-stroke"}}Add newsletter \ No newline at end of file diff --git a/ghost/admin/app/components/settings/members-email-labs/newsletter-management.js b/ghost/admin/app/components/settings/members-email-labs/newsletter-management.js index 7fd0c05228..7aa1939b0b 100644 --- a/ghost/admin/app/components/settings/members-email-labs/newsletter-management.js +++ b/ghost/admin/app/components/settings/members-email-labs/newsletter-management.js @@ -1,30 +1,37 @@ import Component from '@glimmer/component'; -import {action} from '@ember/object'; import {inject as service} from '@ember/service'; +import {task} from 'ember-concurrency'; export default class NewsletterManagementComponent extends Component { - @service newsletters; @service store; + newsletters = this.store.peekAll('newsletter'); + + constructor() { + super(...arguments); + this.loadNewslettersTask.perform(); + } + get activeNewsletters() { - return this.newsletters.newsletters.filter(n => n.status === 'active'); + return this.newsletters.filter(n => n.status === 'active'); } get archivedNewsletters() { - return this.newsletters.newsletters.filter(n => n.status === 'archived'); + return this.newsletters.filter(n => n.status === 'archived'); } get hasMultiple() { return this.activeNewsletters.length > 1; } - @action - addNewsletter() { - this.newsletters.add(); + @task + *archiveNewsletterTask(newsletter) { + newsletter.status = 'archived'; + return yield newsletter.save(); } - @action - archiveNewsletter(id) { - this.newsletters.archive(id); + @task + *loadNewslettersTask() { + return yield this.store.findAll('newsletter'); } } diff --git a/ghost/admin/app/controllers/settings/members-email-labs.js b/ghost/admin/app/controllers/settings/members-email-labs.js index 2d9ca0d864..c73308202f 100644 --- a/ghost/admin/app/controllers/settings/members-email-labs.js +++ b/ghost/admin/app/controllers/settings/members-email-labs.js @@ -9,48 +9,16 @@ export default class MembersEmailLabsController extends Controller { @service session; @service settings; - queryParams = ['showEmailDesignSettings']; - // from/supportAddress are set here so that they can be reset to saved values on save // to avoid it looking like they've been saved when they have a separate update process @tracked fromAddress = ''; @tracked supportAddress = ''; - @tracked showEmailDesignSettings = false; - @tracked showLeaveSettingsModal = false; - @action setEmailAddress(property, email) { this[property] = email; } - @action - toggleEmailDesignSettings() { - this.showEmailDesignSettings = !this.showEmailDesignSettings; - } - - leaveRoute(transition) { - if (this.settings.get('hasDirtyAttributes')) { - transition.abort(); - this.leaveSettingsTransition = transition; - this.showLeaveSettingsModal = true; - } - this.showEmailDesignSettings = false; - } - - @action - async confirmLeave() { - this.settings.rollbackAttributes(); - this.showLeaveSettingsModal = false; - this.leaveSettingsTransition.retry(); - } - - @action - cancelLeave() { - this.showLeaveSettingsModal = false; - this.leaveSettingsTransition = null; - } - parseEmailAddress(address) { const emailAddress = address || 'noreply'; // Adds default domain as site domain diff --git a/ghost/admin/app/helpers/full-email-address.js b/ghost/admin/app/helpers/full-email-address.js index 8c332096e0..06d44d2904 100644 --- a/ghost/admin/app/helpers/full-email-address.js +++ b/ghost/admin/app/helpers/full-email-address.js @@ -4,7 +4,7 @@ import {inject as service} from '@ember/service'; export default class FullEmailAddressHelper extends Helper { @service config; - compute([email]) { + compute([email = '']) { if (email.indexOf('@') > -1) { return email; } diff --git a/ghost/admin/app/mixins/validation-engine.js b/ghost/admin/app/mixins/validation-engine.js index fc3a809cb3..fd6815338f 100644 --- a/ghost/admin/app/mixins/validation-engine.js +++ b/ghost/admin/app/mixins/validation-engine.js @@ -9,6 +9,7 @@ import MemberValidator from 'ghost-admin/validators/member'; import Mixin from '@ember/object/mixin'; import Model from '@ember-data/model'; import NavItemValidator from 'ghost-admin/validators/nav-item'; +import NewsletterValidator from 'ghost-admin/validators/newsletter'; import OfferValidator from 'ghost-admin/validators/offer'; import PostValidator from 'ghost-admin/validators/post'; import ProductBenefitItemValidator from 'ghost-admin/validators/product-benefit-item'; @@ -75,7 +76,8 @@ export default Mixin.create({ label: LabelValidator, snippet: SnippetValidator, product: ProductValidator, - offer: OfferValidator + offer: OfferValidator, + newsletter: NewsletterValidator }; }, diff --git a/ghost/admin/app/models/newsletter.js b/ghost/admin/app/models/newsletter.js new file mode 100644 index 0000000000..99769f3ba2 --- /dev/null +++ b/ghost/admin/app/models/newsletter.js @@ -0,0 +1,32 @@ +import Model, {attr} from '@ember-data/model'; +import ValidationEngine from '../mixins/validation-engine'; + +export default class Newsletter extends Model.extend(ValidationEngine) { + validationType = 'newsletter'; + + @attr name; + @attr description; + + @attr senderName; + @attr senderEmail; + @attr senderReplyTo; + + @attr({defaultValue: 'active'}) status; + @attr({defaultValue: ''}) recipientFilter; + @attr({defaultValue: false}) subscribeOnSignup; + @attr({defaultValue: 0}) sortOrder; + + // Design-related properties - TODO: not currently supported in API + @attr headerImage; + @attr({defaultValue: true}) showHeaderIcon; + @attr({defaultValue: true}) showHeaderTitle; + @attr({defaultValue: 'sans_serif'}) titleFontCategory; + @attr({defaultValue: 'center'}) titleAlignment; + @attr({defaultValue: true}) showFeatureImage; + @attr({defaultValue: 'sans_serif'}) bodyFontCategory; + @attr() footerContent; + @attr({defaultValue: true}) showBadge; + + // TODO: delete attr, incorrectly needed for save to complete in API + @attr({defaultValue: false}) default; +} diff --git a/ghost/admin/app/router.js b/ghost/admin/app/router.js index 02b35fbd95..c39e840dc9 100644 --- a/ghost/admin/app/router.js +++ b/ghost/admin/app/router.js @@ -43,9 +43,13 @@ Router.map(function () { this.route('settings.general', {path: '/settings/general'}); this.route('settings.membership', {path: '/settings/members'}); this.route('settings.members-email', {path: '/settings/members-email'}); - this.route('settings.members-email-labs', {path: '/settings/members-email-labs'}); this.route('settings.code-injection', {path: '/settings/code-injection'}); + this.route('settings.members-email-labs', {path: '/settings/members-email-labs'}, function () { + this.route('new-newsletter', {path: '/newsletters/new'}); + this.route('edit-newsletter', {path: '/newsletters/:newsletter_id'}); + }); + this.route('settings.design', {path: '/settings/design'}, function () { this.route('change-theme', function () { this.route('view', {path: ':theme_name'}); diff --git a/ghost/admin/app/routes/settings/members-email-labs.js b/ghost/admin/app/routes/settings/members-email-labs.js index 1fe472ea14..32c9c5a225 100644 --- a/ghost/admin/app/routes/settings/members-email-labs.js +++ b/ghost/admin/app/routes/settings/members-email-labs.js @@ -1,12 +1,17 @@ import AdminRoute from 'ghost-admin/routes/admin'; +import ConfirmUnsavedChangesModal from '../../components/modals/confirm-unsaved-changes'; import {action} from '@ember/object'; import {inject as service} from '@ember/service'; export default class MembersEmailLabsRoute extends AdminRoute { @service feature; + @service modals; @service notifications; @service settings; + confirmModal = null; + hasConfirmed = false; + beforeModel(transition) { super.beforeModel(...arguments); @@ -32,7 +37,44 @@ export default class MembersEmailLabsRoute extends AdminRoute { @action willTransition(transition) { - return this.controller.leaveRoute(transition); + if (this.hasConfirmed) { + return true; + } + + // always abort when not confirmed because Ember's router doesn't automatically wait on promises + transition.abort(); + + this.confirmUnsavedChanges().then((shouldLeave) => { + if (shouldLeave) { + this.hasConfirmed = true; + return transition.retry(); + } + }); + } + + deactivate() { + this.confirmModal = null; + this.hasConfirmed = false; + } + + confirmUnsavedChanges() { + if (!this.settings.get('hasDirtyAttributes')) { + return Promise.resolve(true); + } + + if (!this.confirmModal) { + this.confirmModal = this.modals.open(ConfirmUnsavedChangesModal) + .then((discardChanges) => { + if (discardChanges === true) { + this.settings.rollbackAttributes(); + } + return discardChanges; + }).finally(() => { + this.confirmModal = null; + }); + } + + return this.confirmModal; } buildRouteInfoMetadata() { diff --git a/ghost/admin/app/routes/settings/members-email-labs/edit-newsletter.js b/ghost/admin/app/routes/settings/members-email-labs/edit-newsletter.js new file mode 100644 index 0000000000..9b923661be --- /dev/null +++ b/ghost/admin/app/routes/settings/members-email-labs/edit-newsletter.js @@ -0,0 +1,47 @@ +import AdminRoute from 'ghost-admin/routes/admin'; +import EditNewsletterModal from 'ghost-admin/components/modals/edit-newsletter'; +import {action} from '@ember/object'; +import {inject as service} from '@ember/service'; + +export default class EditNewsletterRoute extends AdminRoute { + @service modals; + @service router; + @service store; + + newsletterModal = null; + + model(params) { + return this.store.find('newsletter', params.newsletter_id); + } + + setupController(controller, model) { + this.newsletterModal?.close(); + + this.newsletterModal = this.modals.open(EditNewsletterModal, { + newsletter: model, + afterSave: this.afterSave + }, { + beforeClose: this.beforeModalClose + }); + } + + deactivate() { + this.isLeaving = true; + this.newsletterModal?.close(); + + this.isLeaving = false; + this.newsletterModal = null; + } + + @action + afterSave() { + this.router.transitionTo('settings.members-email-labs'); + } + + @action + beforeModalClose() { + if (this.newsletterModal && !this.isLeaving) { + this.router.transitionTo('settings.members-email-labs'); + } + } +} diff --git a/ghost/admin/app/routes/settings/members-email-labs/new-newsletter.js b/ghost/admin/app/routes/settings/members-email-labs/new-newsletter.js new file mode 100644 index 0000000000..6eb9586343 --- /dev/null +++ b/ghost/admin/app/routes/settings/members-email-labs/new-newsletter.js @@ -0,0 +1,47 @@ +import AdminRoute from 'ghost-admin/routes/admin'; +import EditNewsletterModal from 'ghost-admin/components/modals/edit-newsletter'; +import {action} from '@ember/object'; +import {inject as service} from '@ember/service'; + +export default class NewNewsletterRoute extends AdminRoute { + @service modals; + @service router; + @service store; + + newsletterModal = null; + + model() { + return this.store.createRecord('newsletter'); + } + + setupController(controller, model) { + this.newsletterModal?.close(); + + this.newsletterModal = this.modals.open(EditNewsletterModal, { + newsletter: model, + afterSave: this.afterSave + }, { + beforeClose: this.beforeModalClose + }); + } + + deactivate() { + this.isLeaving = true; + this.newsletterModal?.close(); + + this.isLeaving = false; + this.newsletterModal = null; + } + + @action + afterSave() { + this.router.transitionTo('settings.members-email-labs'); + } + + @action + beforeModalClose() { + if (this.newsletterModal && !this.isLeaving) { + this.router.transitionTo('settings.members-email-labs'); + } + } +} diff --git a/ghost/admin/app/services/newsletters.js b/ghost/admin/app/services/newsletters.js deleted file mode 100644 index a67303c464..0000000000 --- a/ghost/admin/app/services/newsletters.js +++ /dev/null @@ -1,94 +0,0 @@ -import Service, {inject as service} from '@ember/service'; -import {TrackedArray} from 'tracked-built-ins'; -import {set} from '@ember/object'; -import {tracked} from '@glimmer/tracking'; - -class Newsletter { - @tracked name; - @tracked description; - @tracked sender_name; - @tracked sender_email; - @tracked sender_reply_to; - @tracked default; - @tracked status; - @tracked recipient_filter; - @tracked subscribe_on_signup; - @tracked sort_order; - - constructor(obj) { - Object.assign(this, obj); - } -} - -let counter = 0; -function getFakeNewsletter() { - counter += 1; - return new Newsletter({ - id: Math.floor(Math.random() * 1e9), - name: 'Daily roundup ' + counter, - description: 'Daily news delivered to your inbox every morning.', - sender_name: 'Test', - sender_email: 'test@example.com', - sender_reply_to: 'test@example.com', - default: false, - status: 'active', - recipient_filter: '', - subscribe_on_signup: true, - sort_order: counter, - members: { - total: Math.floor(Math.random() * 100) - }, - posts: { - total: Math.floor(Math.random() * 100) - } - }); -} - -export default class NewslettersService extends Service { - @service config; - @service settings; - @service feature; - @service store; - - newsletters = new TrackedArray([ - new Newsletter({ - id: '123', - name: 'Daily roundup', - description: 'Daily news delivered to your inbox every morning.', - sender_name: 'Test', - sender_email: 'test@example.com', - sender_reply_to: 'test@example.com', - default: true, - status: 'active', - recipient_filter: '', - subscribe_on_signup: true, - sort_order: 0, - members: { - total: 19 - }, - posts: { - total: 17 - } - }) - ]); - - add() { - this.newsletters.push(getFakeNewsletter()); - this.newsletters.sort((a,b) => a.sort_order - b.sort_order); - return this.newsletters; - } - - archive(newsletterId) { - const newsletter = this.newsletters.find(n => n.id === newsletterId); - if (newsletter) { - set(newsletter, 'status', 'archived'); - } - } - - unArchive(newsletterId) { - const newsletter = this.newsletters.find(n => n.id === newsletterId); - if (newsletter) { - set(newsletter, 'status', 'active'); - } - } -} diff --git a/ghost/admin/app/templates/settings/members-email-labs.hbs b/ghost/admin/app/templates/settings/members-email-labs.hbs index e472b005f5..1bcc332c3b 100644 --- a/ghost/admin/app/templates/settings/members-email-labs.hbs +++ b/ghost/admin/app/templates/settings/members-email-labs.hbs @@ -24,25 +24,7 @@ @fromAddress={{this.fromAddress}} @supportAddress={{this.supportAddress}} @setEmailAddress={{this.setEmailAddress}} - @toggleEmailDesignSettings={{this.toggleEmailDesignSettings}} />
    - - {{#if this.showLeaveSettingsModal}} - - {{/if}} - - -{{#if this.showEmailDesignSettings}} - - - -{{/if}} \ No newline at end of file + \ No newline at end of file diff --git a/ghost/admin/app/validators/newsletter.js b/ghost/admin/app/validators/newsletter.js new file mode 100644 index 0000000000..92f88467fe --- /dev/null +++ b/ghost/admin/app/validators/newsletter.js @@ -0,0 +1,69 @@ +import BaseValidator from './base'; +import validator from 'validator'; +import {isBlank} from '@ember/utils'; + +export default BaseValidator.create({ + properties: ['name', 'senderName', 'senderEmail', 'senderReplyTo'], + + name(model) { + if (isBlank(model.name)) { + model.errors.add('name', 'Please enter a name.'); + this.invalidate(); + } + + if (!validator.isLength(model.name || '', 0, 191)) { + model.errors.add('name', 'Name cannot be longer than 191 characters.'); + this.invalidate(); + } + + model.hasValidated.addObject('name'); + }, + + senderName(model) { + if (isBlank(model.senderName)) { + model.errors.add('senderName', 'Please enter a sender name.'); + this.invalidate(); + } + + if (!validator.isLength(model.senderName || '', 0, 191)) { + model.errors.add('senderName', 'Sender name cannot be longer than 191 characters.'); + this.invalidate(); + } + + model.hasValidated.addObject('senderName'); + }, + + senderEmail(model) { + if (isBlank(model.senderEmail)) { + model.errors.add('senderEmail', 'Please enter a newsletter email address.'); + this.invalidate(); + } else if (!validator.isEmail(model.senderEmail)) { + model.errors.add('senderEmail', 'Invalid email.'); + this.invalidate(); + } + + if (!validator.isLength(model.senderEmail || '', 0, 191)) { + model.errors.add('senderEmail', 'Sender email cannot be longer than 191 characters.'); + this.invalidate(); + } + + model.hasValidated.addObject('senderEmail'); + }, + + senderReplyTo(model) { + if (isBlank(model.senderReplyTo)) { + model.errors.add('senderReplyTo', 'Please enter a reply-to email address.'); + this.invalidate(); + } else if (!validator.isEmail(model.senderReplyTo)) { + model.errors.add('senderReplyTo', 'Invalid email.'); + this.invalidate(); + } + + if (!validator.isLength(model.senderReplyTo || '', 0, 191)) { + model.errors.add('senderReplyTo', 'Reply-to email cannot be longer than 191 characters.'); + this.invalidate(); + } + + model.hasValidated.addObject('senderReplyTo'); + } +}); diff --git a/ghost/admin/mirage/config.js b/ghost/admin/mirage/config.js index 55ec07f1bc..5ad51cd4af 100644 --- a/ghost/admin/mirage/config.js +++ b/ghost/admin/mirage/config.js @@ -8,6 +8,7 @@ import mockIntegrations from './config/integrations'; import mockInvites from './config/invites'; import mockLabels from './config/labels'; import mockMembers from './config/members'; +import mockNewsletters from './config/newsletters'; import mockOffers from './config/offers'; import mockPages from './config/pages'; import mockPosts from './config/posts'; @@ -38,7 +39,7 @@ export default function () { // this.put('/posts/:id/', versionMismatchResponse); // mockTags(this); // this.loadFixtures('settings'); - mockSnippets(this); + mockNewsletters(this); // keep this line, it allows all other API requests to hit the real server this.passthrough(); @@ -78,6 +79,8 @@ export function testConfig() { mockWebhooks(this); mockProducts(this); mockOffers(this); + mockSnippets(this); + mockNewsletters(this); /* Notifications -------------------------------------------------------- */ diff --git a/ghost/admin/mirage/config/newsletters.js b/ghost/admin/mirage/config/newsletters.js new file mode 100644 index 0000000000..1ef90ccd40 --- /dev/null +++ b/ghost/admin/mirage/config/newsletters.js @@ -0,0 +1,8 @@ +import {paginatedResponse} from '../utils'; + +export default function mockNewsletters(server) { + server.post('/newsletters/'); + server.get('/newsletters/', paginatedResponse('newsletters')); + server.get('/newsletters/:id/'); + server.put('/newsletters/:id/'); +}