diff --git a/ghost/admin/app/adapters/post.js b/ghost/admin/app/adapters/post.js index 86bd0846b7..9af059847b 100644 --- a/ghost/admin/app/adapters/post.js +++ b/ghost/admin/app/adapters/post.js @@ -10,9 +10,7 @@ export default class Post extends ApplicationAdapter { const newsletter = snapshot.adapterOptions.newsletter; parsedUrl.searchParams.append('newsletter', newsletter); - // TODO: cleanup sendEmailWhenPublished when removing publishingFlow flag - let emailSegment = snapshot?.adapterOptions?.emailSegment - || snapshot?.adapterOptions?.sendEmailWhenPublished; + let emailSegment = snapshot?.adapterOptions?.emailSegment; if (emailSegment) { if (emailSegment === 'status:free,status:-free') { diff --git a/ghost/admin/app/components/gh-distribution-action-select.hbs b/ghost/admin/app/components/gh-distribution-action-select.hbs deleted file mode 100644 index 858291ff6f..0000000000 --- a/ghost/admin/app/components/gh-distribution-action-select.hbs +++ /dev/null @@ -1,17 +0,0 @@ - - - {{availablePublishAction.name}} - - \ No newline at end of file diff --git a/ghost/admin/app/components/gh-distribution-action-select.js b/ghost/admin/app/components/gh-distribution-action-select.js deleted file mode 100644 index d597f3267f..0000000000 --- a/ghost/admin/app/components/gh-distribution-action-select.js +++ /dev/null @@ -1,24 +0,0 @@ -import Component from '@glimmer/component'; -import {action} from '@ember/object'; - -export default class GhDistributionActionSelect extends Component { - availablePublishActions = [{ - value: 'publish_send', - name: 'publish & send' - }, { - value: 'publish', - name: 'publish' - }, { - value: 'send', - name: 'send' - }]; - - get distributionValue() { - return this.availablePublishActions.findBy('value', this.args.distributionAction); - } - - @action - setDistributionAction(newAction) { - this.args.setDistributionAction(newAction.value); - } -} diff --git a/ghost/admin/app/components/gh-publishmenu-draft.hbs b/ghost/admin/app/components/gh-publishmenu-draft.hbs deleted file mode 100644 index d2096fa7a0..0000000000 --- a/ghost/admin/app/components/gh-publishmenu-draft.hbs +++ /dev/null @@ -1,94 +0,0 @@ -
-
Ready to - {{#if @canSendEmail}} - - {{else}} - publish - {{/if}} - this {{@post.displayName}}? -
-
-
-
-
-
-
{{#if @emailOnly}}Send email now{{else}}Set it live now{{/if}}
-
{{#if @emailOnly}}Deliver this immediately{{else}}Publish this {{@post.displayName}} immediately{{/if}}
-
-
-
-
-
-
Schedule it for later
- -
{{#if @emailOnly}}Send email at a specific time{{else}}Set automatic future publish date{{/if}}
-
-
-
- - {{#if this.showEmailSection}} -
-
- {{#if @isSendingEmailLimited}} -

{{html-safe @sendingEmailLimitError}}

- {{else}} -
- - - {{#if this.disableEmailOption}} -

- - Add members - - to start sending newsletters! -

- {{else}} -
- {{#if (and (feature "multipleNewsletters") (gt @availableNewsletters.length 1))}} -
- - {{newsletter.name}} - -
- {{/if}} - - -
- {{/if}} -
- {{/if}} -
-
- {{/if}} -
-
diff --git a/ghost/admin/app/components/gh-publishmenu-draft.js b/ghost/admin/app/components/gh-publishmenu-draft.js deleted file mode 100644 index a9949bc17e..0000000000 --- a/ghost/admin/app/components/gh-publishmenu-draft.js +++ /dev/null @@ -1,123 +0,0 @@ -import Component from '@glimmer/component'; -import moment from 'moment'; -import {action} from '@ember/object'; -import {isEmpty} from '@ember/utils'; -import {inject as service} from '@ember/service'; -import {task} from 'ember-concurrency'; -import {tracked} from '@glimmer/tracking'; - -export default class GhPublishMenuDraftComponent extends Component { - @service config; - @service feature; - @service session; - @service settings; - @service store; - - @tracked totalMemberCount = null; - - // used to set minDate in datepicker - _minDate = null; - _publishedAtBlogTZ = null; - - get disableEmailOption() { - // TODO: remove owner or admin check when editors can count members - return this.session.user.isAdmin && (this.totalMemberCount === 0); - } - - get showEmailSection() { - return this.args.canSendEmail && this.args.distributionAction !== 'publish'; - } - - constructor() { - super(...arguments); - this.args.post.set('publishedAtBlogTZ', this.args.post.publishedAtUTC); - - this._updateDatesForSaveType(this.args.saveType); - } - - @action - setSaveType(type) { - if (this.args.saveType !== type) { - this._updateDatesForSaveType(type); - this.args.setSaveType(type); - this.args.post.validate(); - } - } - - @action - setDistributionAction(type) { - this.args.setDistributionAction(type); - } - - @action - setDate(date) { - let post = this.args.post; - let dateString = moment(date).format('YYYY-MM-DD'); - - post.set('publishedAtBlogDate', dateString); - return post.validate(); - } - - @action - setTime(time) { - let post = this.args.post; - - post.set('publishedAtBlogTime', time); - return post.validate(); - } - - // the date-time-picker component has it's own error handling for - // invalid date and times but in this case we want the values to make it - // to the model to make that invalid - @action - dateInputDidError(date) { - this.setDate(date); - } - - @action - timeInputDidError(time) { - this.setTime(time); - } - - @task - *countTotalMembersTask() { - const user = yield this.session.user; - - if (user.isAdmin) { - const result = yield this.store.query('member', {limit: 1, filter: 'newsletters.status:active'}); - this.totalMemberCount = result.meta.pagination.total; - } - } - - _updateDatesForSaveType(type) { - let hasDateError = !isEmpty(this.args.post.errors.errorsFor('publishedAtBlogDate')); - let hasTimeError = !isEmpty(this.args.post.errors.errorsFor('publishedAtBlogTime')); - - let minDate = this._getMinDate(); - this._minDate = minDate; - - // when publish: switch to now to avoid validation errors - // when schedule: switch to last valid or new minimum scheduled date - if (type === 'publish') { - if (!hasDateError && !hasTimeError) { - this._publishedAtBlogTZ = this.args.post.publishedAtBlogTZ; - } else { - this._publishedAtBlogTZ = this.args.post.publishedAtUTC; - } - - this.args.post.set('publishedAtBlogTZ', this.args.post.publishedAtUTC); - } else { - if (!this._publishedAtBlogTZ || moment(this._publishedAtBlogTZ).isBefore(minDate)) { - this.args.post.set('publishedAtBlogTZ', minDate); - } else { - this.args.post.set('publishedAtBlogTZ', this._publishedAtBlogTZ); - } - } - } - - // API only accepts dates at least 2 mins in the future, default the - // scheduled date 5 mins in the future to avoid immediate validation errors - _getMinDate() { - return moment.utc().add(5, 'minutes'); - } -} diff --git a/ghost/admin/app/components/gh-publishmenu-published.hbs b/ghost/admin/app/components/gh-publishmenu-published.hbs deleted file mode 100644 index 4d9da4b397..0000000000 --- a/ghost/admin/app/components/gh-publishmenu-published.hbs +++ /dev/null @@ -1,19 +0,0 @@ -
-
Update {{@post.displayName}} status
-
-
-
-
-
Unpublished
-
Revert this {{@post.displayName}} to a private draft
-
-
-
-
-
-
Published
-
Display this {{@post.displayName}} publicly
-
-
-
-
\ No newline at end of file diff --git a/ghost/admin/app/components/gh-publishmenu-scheduled.hbs b/ghost/admin/app/components/gh-publishmenu-scheduled.hbs deleted file mode 100644 index 7f07dd74ae..0000000000 --- a/ghost/admin/app/components/gh-publishmenu-scheduled.hbs +++ /dev/null @@ -1,72 +0,0 @@ -
-
Will be {{if @emailOnly "sent" "published"}} in {{this.timeToPublished}}
-
-
-
-
-
-
Revert to draft
-
Do not publish
-
-
-
-
-
-
Schedule for later
- -
Set automatic future publish date
-
-
-
- {{#if @canSendEmail}} -
-
- {{#if @isSendingEmailLimited}} -

{{html-safe @sendingEmailLimitError}}

- {{else}} -
- - - {{#if (and (feature "multipleNewsletters") (gt @availableNewsletters.length 1))}} -
- - {{newsletter.name}} - -
- {{/if}} - -
- -
-
- {{/if}} -
-
- {{/if}} -
-
diff --git a/ghost/admin/app/components/gh-publishmenu-scheduled.js b/ghost/admin/app/components/gh-publishmenu-scheduled.js deleted file mode 100644 index 8a9dcee32f..0000000000 --- a/ghost/admin/app/components/gh-publishmenu-scheduled.js +++ /dev/null @@ -1,74 +0,0 @@ -import Component from '@glimmer/component'; -import moment from 'moment'; -import {action} from '@ember/object'; -import {inject as service} from '@ember/service'; -import {tracked} from '@glimmer/tracking'; - -export default class GhPublishmenuScheduledComponent extends Component { - @service clock; - @service session; - @service feature; - @service settings; - @service config; - - // used to set minDate in datepicker - @tracked minDate = null; - - get timeToPublished() { - let publishedAtUTC = this.args.post.publishedAtUTC; - - if (!publishedAtUTC) { - return null; - } - - this.clock.get('second'); - - return publishedAtUTC.toNow(true); - } - - get selectedNewsletter() { - return this.args.availableNewsletters.find(n => n.id === this.args.post.newsletter?.id); - } - - constructor() { - super(...arguments); - this.minDate = new Date(); - } - - @action - setSaveType(type) { - if (this.args.saveType !== type) { - this.minDate = new Date(); - this.args.setSaveType(type); - - // when draft switch to now to avoid validation errors - // when schedule switch back to saved date to avoid unnecessary re-scheduling - if (type === 'draft') { - this.args.post.set('publishedAtBlogTZ', new Date()); - } else { - this.args.post.set('publishedAtBlogTZ', this.args.post.publishedAtUTC); - } - - this.args.post.validate(); - } - } - - @action - setDate(date) { - let post = this.args.post; - let dateString = moment(date).format('YYYY-MM-DD'); - - post.set('publishedAtBlogDate', dateString); - return post.validate(); - } - - @action - setTime(time) { - let post = this.args.post; - - if (!this.args.isClosing) { - post.set('publishedAtBlogTime', time); - return post.validate(); - } - } -} diff --git a/ghost/admin/app/components/gh-publishmenu.hbs b/ghost/admin/app/components/gh-publishmenu.hbs deleted file mode 100644 index d582c5e0f8..0000000000 --- a/ghost/admin/app/components/gh-publishmenu.hbs +++ /dev/null @@ -1,78 +0,0 @@ -{{#if (eq this.displayState "sent")}} -
Sent
-{{else}} - - - {{this.triggerText}} {{svg-jar "arrow-down"}} - - - - {{#if (eq this.displayState "published")}} - - - {{else if (eq this.displayState "scheduled")}} - - - {{else}} - - {{/if}} - - {{!-- - save button needs to be outside of menu components so it doesn't lose state - or cancel the task when the post status updates and switches components - --}} - - - -{{/if}} - -{{!-- - Workaround to have an always-shown element to attach key handlers to. - TODO: Move onto main element once converted to a glimmer component ---}} - \ No newline at end of file diff --git a/ghost/admin/app/components/gh-publishmenu.js b/ghost/admin/app/components/gh-publishmenu.js deleted file mode 100644 index 8afa6e91e9..0000000000 --- a/ghost/admin/app/components/gh-publishmenu.js +++ /dev/null @@ -1,537 +0,0 @@ -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 {or, reads} from '@ember/object/computed'; -import {inject as service} from '@ember/service'; -import {task, timeout} from 'ember-concurrency'; - -const CONFIRM_EMAIL_POLL_LENGTH = 1000; -const CONFIRM_EMAIL_MAX_POLL_LENGTH = 15 * 1000; - -export default Component.extend({ - clock: service(), - config: service(), - feature: service(), - limit: service(), - modals: service(), - session: service(), - settings: service(), - store: service(), - - classNames: 'gh-publishmenu', - displayState: 'draft', - saveType: 'publish', - post: null, - postStatus: 'draft', - distributionAction: 'publish_send', - runningText: null, - saveTask: null, - sendEmailWhenPublished: null, - typedDateError: null, - isSendingEmailLimited: false, - sendingEmailLimitError: '', - selectedNewsletter: null, - - _publishedAtBlogTZ: null, - _previousStatus: null, - - isClosing: null, - - onClose() {}, - - forcePublishedMenu: reads('post.pastScheduledTime'), - - hasEmailPermission: or('session.user.isOwnerOnly', 'session.user.isAdminOnly', 'session.user.isEditor'), - - emailOnly: computed.equal('distributionAction', 'send'), - - canSendEmail: computed('hasEmailPermission', 'post.{isPost,email}', 'settings.{editorDefaultEmailRecipients,membersSignupAccess,mailgunIsConfigured}', 'config.mailgunIsConfigured', function () { - let isDisabled = this.settings.get('editorDefaultEmailRecipients') === 'disabled' || this.settings.get('membersSignupAccess') === 'none'; - let mailgunIsConfigured = this.settings.get('mailgunIsConfigured') || this.config.get('mailgunIsConfigured'); - let isPost = this.post.isPost; - let hasSentEmail = !!this.post.email; - - return this.hasEmailPermission && - !isDisabled && - mailgunIsConfigured && - isPost && - !hasSentEmail; - }), - - postState: computed('post.{isPublished,isScheduled}', 'forcePublishedMenu', function () { - if (this.forcePublishedMenu || this.get('post.isPublished')) { - return 'published'; - } else if (this.get('post.isScheduled')) { - return 'scheduled'; - } else { - return 'draft'; - } - }), - - triggerText: computed('postState', function () { - let state = this.postState; - - if (state === 'published') { - return 'Update'; - } else if (state === 'scheduled') { - return 'Scheduled'; - } else { - return 'Publish'; - } - }), - - _runningText: computed('postState', 'saveType', function () { - let saveType = this.saveType; - let postState = this.postState; - let runningText; - - if (postState === 'draft') { - runningText = saveType === 'publish' ? 'Publishing' : 'Scheduling'; - } - - if (postState === 'published') { - runningText = saveType === 'publish' ? 'Updating' : 'Unpublishing'; - } - - if (postState === 'scheduled') { - runningText = saveType === 'schedule' ? 'Rescheduling' : 'Unscheduling'; - } - - return runningText || 'Publishing'; - }), - - buttonText: computed('postState', 'saveType', 'distributionAction', 'sendEmailWhenPublished', function () { - let saveType = this.saveType; - let postState = this.postState; - let distributionAction = this.distributionAction; - let buttonText; - - if (postState === 'draft') { - switch (distributionAction) { - case 'publish_send': - if (saveType === 'publish') { - buttonText = 'Publish'; - - if (this.canSendEmail && this.sendEmailWhenPublished && this.sendEmailWhenPublished !== 'none') { - buttonText = `${buttonText} & send`; - } - } else { - buttonText = 'Schedule'; - } - break; - case 'publish': - buttonText = (saveType === 'publish') ? 'Publish' : 'Schedule'; - break; - case 'send': - buttonText = saveType === 'publish' ? 'Send' : 'Schedule'; - break; - } - } - - if (postState === 'published') { - buttonText = saveType === 'publish' ? 'Update' : 'Unpublish'; - } - - if (postState === 'scheduled') { - buttonText = saveType === 'schedule' ? 'Reschedule' : 'Unschedule'; - } - - return buttonText || 'Publish'; - }), - - successText: computed('_previousStatus', 'postState', function () { - let postState = this.postState; - let previousStatus = this._previousStatus; - let buttonText; - - if (previousStatus === 'draft') { - buttonText = postState === 'published' ? 'Published' : 'Scheduled'; - } - - if (previousStatus === 'published') { - buttonText = postState === 'draft' ? 'Unpublished' : 'Updated'; - } - - if (previousStatus === 'scheduled') { - buttonText = postState === 'draft' ? 'Unscheduled' : 'Rescheduled'; - } - - return buttonText; - }), - - defaultEmailRecipients: computed('settings.{editorDefaultEmailRecipients,editorDefaultEmailRecipientsFilter}', 'post.visibility', function () { - const defaultEmailRecipients = this.settings.get('editorDefaultEmailRecipients'); - - if (defaultEmailRecipients === 'disabled') { - return null; - } - - if (defaultEmailRecipients === 'visibility') { - if (this.post.visibility === 'public') { - return 'status:free,status:-free'; - } - - if (this.post.visibility === 'members') { - return 'status:free,status:-free'; - } - - if (this.post.visibility === 'paid') { - return 'status:-free'; - } - - if (this.post.visibility === 'tiers') { - return this.post.visibilitySegment; - } - - return this.post.visibility; - } - - return this.settings.get('editorDefaultEmailRecipientsFilter'); - }), - - didReceiveAttrs() { - this._super(...arguments); - - // update the displayState based on the post status but only after a - // save has finished to avoid swapping the menu prematurely and triggering - // calls to `setSaveType` due to the component re-rendering - // TODO: we should have a better way of dealing with this where we don't - // rely on the side-effect of component rendering calling setSaveType - let postStatus = this.postStatus; - if (postStatus !== this._postStatus) { - if (this.get('saveTask.isRunning')) { - this.get('saveTask.last').then(() => { - this.set('displayState', postStatus); - this.updateSaveTypeForPostStatus(postStatus); - }); - } else { - this.set('displayState', postStatus); - this.updateSaveTypeForPostStatus(postStatus); - } - } - - this._postStatus = this.postStatus; - this.setDefaultSendEmailWhenPublished(); - this.checkIsSendingEmailLimitedTask.perform(); - - const defaultEmailRecipients = this.get('defaultEmailRecipients'); - - if (this.post.status === 'scheduled' && this.post.emailOnly) { - this.set('distributionAction', 'send'); - } - - if (this.post.isPage || !defaultEmailRecipients) { - this.set('distributionAction', 'publish'); - } - }, - - didInsertElement() { - this._super(...arguments); - this.fetchNewslettersTask.perform(); - }, - - actions: { - setSaveType(saveType) { - let post = this.post; - - this.set('saveType', saveType); - - if (saveType === 'draft') { - post.set('statusScratch', 'draft'); - } else if (saveType === 'schedule') { - post.set('statusScratch', 'scheduled'); - } else if (saveType === 'publish') { - post.set('statusScratch', 'published'); - } - }, - - setSendEmailWhenPublished(sendEmailWhenPublished) { - this.set('sendEmailWhenPublished', sendEmailWhenPublished); - }, - - setDistributionAction(distributionAction) { - this.set('distributionAction', distributionAction); - - if (distributionAction === 'publish') { - this.set('sendEmailWhenPublished', 'none'); - } else { - this.set('sendEmailWhenPublished', this.defaultEmailRecipients); - } - }, - - open() { - this._cachePublishedAtBlogTZ(); - this.set('isClosing', false); - this.get('post.errors').clear(); - - this.setDefaultSendEmailWhenPublished(); - - if (this.onOpen) { - this.onOpen(); - } - }, - - close(dropdown, e) { - // don't close the menu if the datepicker popup or confirm modal is clicked - if (e) { - let onDatepicker = !!e.target.closest('.ember-power-datepicker-content'); - let onModal = !!e.target.closest('.fullscreen-modal-container'); - - if (onDatepicker || onModal) { - return false; - } - } - - if (!this._skipDropdownCloseCleanup) { - this._cleanup(); - } - this._skipDropdownCloseCleanup = false; - - this.onClose(); - this.set('isClosing', true); - - return true; - }, - - publishFromShortcut() { - // trigger blur for inputs and textareas to trigger any actions - // before attempting to save so we're saving after the result - if (document.activeElement?.matches('input[type="text"], textarea')) { - // trigger focusout so that it bubbles - const focusout = new Event('focusout'); - document.activeElement.dispatchEvent(focusout); - - // make sure blur event is triggered too - document.activeElement.blur(); - } - - // wait for actions to be triggered by the focusout/blur before saving - schedule('actions', this, function () { - this.send('setSaveType', 'publish'); - this.save.perform(); - }); - } - }, - - get availableNewsletters() { - return this.store.peekAll('newsletter').filter(n => n.status === 'active'); - }, - - updateSaveTypeForPostStatus(status) { - if (status === 'draft' || status === 'published') { - this.set('saveType', 'publish'); - } - if (status === 'scheduled') { - this.set('saveType', 'schedule'); - } - }, - - setDefaultSendEmailWhenPublished() { - if (this.isSendingEmailLimited) { - this.set('sendEmailWhenPublished', false); - } else if (this.postStatus === 'draft' && this.canSendEmail) { - // Set default newsletter recipients - this.set('sendEmailWhenPublished', this.defaultEmailRecipients); - } else { - this.set('sendEmailWhenPublished', this.post.emailSegment); - } - }, - - checkIsSendingEmailLimitedTask: task(function* () { - try { - yield this.reloadSettingsTask.perform(); - - if (this.limit.limiter && this.limit.limiter.isLimited('emails')) { - yield this.limit.limiter.errorIfWouldGoOverLimit('emails'); - } else if (this.settings.get('emailVerificationRequired')) { - this.set('isSendingEmailLimited', true); - this.set('sendingEmailLimitError', 'Email sending is temporarily disabled because your account is currently in review. You should have an email about this from us already, but you can also reach us any time at support@ghost.org.'); - this.set('sendEmailWhenPublished', 'none'); - return; - } - - this.set('isSendingEmailLimited', false); - this.set('sendingEmailLimitError', null); - } catch (error) { - this.set('isSendingEmailLimited', true); - this.set('sendingEmailLimitError', error.message); - this.set('sendEmailWhenPublished', 'none'); - } - }), - - reloadSettingsTask: task(function* () { - yield this.settings.reload(); - }), - - save: task(function* (options = {}) { - const {post, saveType} = this; - - // don't allow save if an invalid schedule date is present - if (this.typedDateError) { - return false; - } - - // validate publishedAtBlog to avoid an alert when saving for already displayed errors - // important to do this before opening email confirmation modal too - try { - yield post.validate({property: 'publishedAtBlog'}); - } catch (error) { - // re-throw if we don't have a validation error - if (error) { - throw error; - } - return false; - } - - const isPublishOnly = this.distributionAction === 'publish' - || this.sendEmailWhenPublished === 'none' - || this.post.displayName === 'page' - || this.post.email; - - // open publish confirmation if post will be published/scheduled and emailed - if (!isPublishOnly && post.status === 'draft' && (saveType === 'publish' || saveType === 'schedule')) { - if (options.dropdown) { - this._skipDropdownCloseCleanup = true; - options.dropdown.actions.close(); - } - - return yield this.modals.open(ConfirmPublishModal, { - post: this.post, - emailOnly: this.emailOnly, - sendEmailWhenPublished: this.sendEmailWhenPublished, - newsletter: this.selectedNewsletter, - isScheduled: saveType === 'schedule', - confirm: this.saveWithConfirmedPublish.perform, - retryEmailSend: this.retryEmailSendTask.perform - }, { - beforeClose: bind(this, this._cleanup) - }); - } - - return yield this._saveTask.perform(options); - }), - - saveWithConfirmedPublish: task(function* () { - return yield this._saveTask.perform(); - }), - - retryEmailSendTask: task(function* () { - if (!this.post.email) { - return; - } - - let email = yield this.post.email.retry(); - - let pollTimeout = 0; - if (email && email.status !== 'submitted') { - while (pollTimeout < CONFIRM_EMAIL_MAX_POLL_LENGTH) { - yield timeout(CONFIRM_EMAIL_POLL_LENGTH); - pollTimeout += CONFIRM_EMAIL_POLL_LENGTH; - - email = yield email.reload(); - - if (email.status === 'submitted') { - break; - } - if (email.status === 'failed') { - throw new EmailFailedError(email.error); - } - } - } - - return email; - }), - - selectNewsletter: action(function (newsletter) { - this.set('selectedNewsletter', newsletter); - }), - - fetchNewslettersTask: task(function* () { - 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, - emailOnly, - sendEmailWhenPublished, - saveType - } = this; - - // runningText needs to be declared before the other states change during the - // save action. - this.set('runningText', this._runningText); - this.set('_previousStatus', this.get('post.status')); - this.setSaveType(saveType); - - try { - // will show alert for non-date related failed validations - post = yield this.saveTask.perform({sendEmailWhenPublished, newsletter: this.selectedNewsletter?.slug, emailOnly}); - - this._cachePublishedAtBlogTZ(); - - if (sendEmailWhenPublished && sendEmailWhenPublished !== 'none') { - 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; - - post = yield post.reload(); - - if (post.email.status === 'submitted') { - break; - } - if (post.email.status === 'failed') { - throw new EmailFailedError(post.email.error); - } - } - } - } - - this._cleanup(); - - return post; - } catch (error) { - // re-throw if we don't have a validation error - if (error) { - throw error; - } - } - }), - - _cachePublishedAtBlogTZ() { - this._publishedAtBlogTZ = this.get('post.publishedAtBlogTZ'); - }, - - _cleanup() { - this.set('selectedNewsletter', this.defaultNewsletter); - - if (this.post.isScheduled && this.post.emailOnly) { - this.set('distributionAction', 'send'); - } else if (this.post.isPage || !this.defaultEmailRecipients) { - this.set('distributionAction', 'publish'); - } else { - this.set('distributionAction', 'publish_send'); - } - - this.updateSaveTypeForPostStatus(this.post.status); - - // when closing the menu we reset the publishedAtBlogTZ date so that the - // unsaved changes made to the scheduled date aren't reflected in the PSM - this.post.set('publishedAtBlogTZ', this._publishedAtBlogTZ); - - this.post.set('statusScratch', null); - this.post.validate(); - } -}); diff --git a/ghost/admin/app/components/modals/editor/confirm-publish.hbs b/ghost/admin/app/components/modals/editor/confirm-publish.hbs deleted file mode 100644 index 985ce29665..0000000000 --- a/ghost/admin/app/components/modals/editor/confirm-publish.hbs +++ /dev/null @@ -1,121 +0,0 @@ - \ No newline at end of file diff --git a/ghost/admin/app/components/modals/editor/confirm-publish.js b/ghost/admin/app/components/modals/editor/confirm-publish.js deleted file mode 100644 index 85255f8421..0000000000 --- a/ghost/admin/app/components/modals/editor/confirm-publish.js +++ /dev/null @@ -1,112 +0,0 @@ -import Component from '@glimmer/component'; -import {action} from '@ember/object'; -import {inject as service} from '@ember/service'; -import {task} from 'ember-concurrency'; -import {tracked} from '@glimmer/tracking'; - -export default class ConfirmPublishModal extends Component { - @service membersCountCache; - @service session; - @service store; - - @tracked errorMessage = null; - @tracked errorDetailsOpen = false; - @tracked memberCount = null; - @tracked memberCountString = null; - - get isEmailOnlyWithNoMembers() { - return this.isEmailOnly && this.memberCount === 0; - } - - get publishAndSendButtonText() { - if (this.isEmailOnly) { - return 'Send'; - } - - if (this.isPublishOnly || this.memberCount === 0) { - return 'Publish'; - } - - return 'Publish and Send'; - } - - constructor() { - super(...arguments); - - // set static up-front so it doesn't change when post is saved and email is created - this.isPublishOnly = this.args.data.sendEmailWhenPublished === 'none' - || this.args.data.post.displayName === 'page' - || this.args.data.post.email; - - this.isEmailOnly = this.args.data.emailOnly; - } - - @action - confirm() { - if (this.errorMessage) { - return this.retryEmailTask.perform(); - } else { - if (!this.countRecipientsTask.isRunning) { - return this.confirmAndCheckErrorTask.perform(); - } - } - } - - @action - toggleErrorDetails() { - this.errorDetailsOpen = !this.errorDetailsOpen; - } - - @task - *countRecipientsTask() { - const {sendEmailWhenPublished,newsletter} = this.args.data; - const filter = `${newsletter.recipientFilter}+(${sendEmailWhenPublished})`; - - this.memberCount = sendEmailWhenPublished ? (yield this.membersCountCache.count(filter)) : 0; - this.memberCountString = sendEmailWhenPublished ? (yield this.membersCountCache.countString(filter, {newsletter})) : '0 members'; - } - - @task - *confirmAndCheckErrorTask() { - try { - yield this.args.data.confirm(); - this.args.close(); - return true; - } catch (e) { - // switch to "failed" state if email fails - if (e && e.name === 'EmailFailedError') { - this.errorMessage = e.message; - return false; - } - - // close modal and continue with normal error handling if it was - // a non-email-related error - this.args.close(); - - if (e) { - throw e; - } - } - } - - @task - *retryEmailTask() { - try { - yield this.args.data.retryEmailSend(); - this.args.close(); - return true; - } catch (e) { - // update "failed" state if email fails again - if (e && e.name === 'EmailFailedError') { - this.errorMessage = e.message; - return; - } - - // TODO: test a non-email failure - maybe this needs to go through - // the notifications service - if (e) { - throw e; - } - } - } -} diff --git a/ghost/admin/app/components/modals/post-preview.hbs b/ghost/admin/app/components/modals/post-preview.hbs deleted file mode 100644 index 2c48dafa8e..0000000000 --- a/ghost/admin/app/components/modals/post-preview.hbs +++ /dev/null @@ -1,43 +0,0 @@ - diff --git a/ghost/admin/app/components/modals/post-preview.js b/ghost/admin/app/components/modals/post-preview.js deleted file mode 100644 index f0c67b0a9f..0000000000 --- a/ghost/admin/app/components/modals/post-preview.js +++ /dev/null @@ -1,40 +0,0 @@ -import Component from '@glimmer/component'; -import {action} from '@ember/object'; -import {inject as service} from '@ember/service'; -import {task} from 'ember-concurrency'; -import {tracked} from '@glimmer/tracking'; - -export default class PostPreviewModal extends Component { - @service settings; - @service session; - - static modalOptions = { - className: 'fullscreen-modal-full-overlay fullscreen-modal-email-preview', - focusTrapOptions: null // not ideal but date inputs aren't focusable otherwise - }; - - @tracked tab = 'browser'; - - constructor() { - super(...arguments); - this.saveFirstTask.perform(); - } - - @action - changeTab(tab) { - this.tab = tab; - } - - @task - *saveFirstTask() { - const {saveTask, post, hasDirtyAttributes} = this.args.data; - - if (saveTask.isRunning) { - return yield saveTask.last; - } - - if (post.isDraft && hasDirtyAttributes) { - yield saveTask.perform(); - } - } -} diff --git a/ghost/admin/app/components/modals/post-preview/browser.hbs b/ghost/admin/app/components/modals/post-preview/browser.hbs deleted file mode 100644 index 7aee445faa..0000000000 --- a/ghost/admin/app/components/modals/post-preview/browser.hbs +++ /dev/null @@ -1,23 +0,0 @@ -
- -
-
- Share preview privately -
- {{@post.previewUrl}} -
- -
- - Open in new tab {{svg-jar "external"}} - -
-
\ No newline at end of file diff --git a/ghost/admin/app/components/modals/post-preview/browser.js b/ghost/admin/app/components/modals/post-preview/browser.js deleted file mode 100644 index 7aeb506727..0000000000 --- a/ghost/admin/app/components/modals/post-preview/browser.js +++ /dev/null @@ -1,11 +0,0 @@ -import Component from '@glimmer/component'; -import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard'; -import {task, timeout} from 'ember-concurrency'; - -export default class ModalPostPreviewBrowserComponent extends Component { - @task - *copyPreviewUrl() { - copyTextToClipboard(this.args.post.previewUrl); - yield timeout(this.isTesting ? 50 : 3000); - } -} diff --git a/ghost/admin/app/components/modals/post-preview/email.hbs b/ghost/admin/app/components/modals/post-preview/email.hbs deleted file mode 100644 index 7df5d59f7b..0000000000 --- a/ghost/admin/app/components/modals/post-preview/email.hbs +++ /dev/null @@ -1,39 +0,0 @@ -
-
-
-

- {{or this.newsletter.senderName this.settings.title}} <{{full-email-address (or this.newsletter.senderEmail "noreply")}}> -

-

To: Jamie Larson <jamie@example.com>

-
- -
-
-
-
- - -
- -
- - {{#if this.sendPreviewEmailError}} -
{{this.sendPreviewEmailError}}
- {{/if}} -
- - -
diff --git a/ghost/admin/app/components/modals/post-preview/email.js b/ghost/admin/app/components/modals/post-preview/email.js deleted file mode 100644 index 809cdb637a..0000000000 --- a/ghost/admin/app/components/modals/post-preview/email.js +++ /dev/null @@ -1,161 +0,0 @@ -import Component from '@glimmer/component'; -import validator from 'validator'; -import {action} from '@ember/object'; -import {htmlSafe} from '@ember/template'; -import {inject as service} from '@ember/service'; -import {task, timeout} from 'ember-concurrency'; -import {tracked} from '@glimmer/tracking'; - -const INJECTED_CSS = ` -html::-webkit-scrollbar { - display: none; - width: 0; - background: transparent -} -html { - scrollbar-width: none; -} -`; - -// TODO: remove duplication with -export default class ModalPostPreviewEmailComponent extends Component { - @service ajax; - @service config; - @service feature; - @service ghostPaths; - @service session; - @service settings; - @service store; - - @tracked html = ''; - @tracked subject = ''; - @tracked memberSegment = 'status:free'; - @tracked previewEmailAddress = this.session.user.email; - @tracked sendPreviewEmailError = ''; - @tracked newsletter = null; - - get mailgunIsEnabled() { - return this.config.get('mailgunIsConfigured') || - !!(this.settings.get('mailgunApiKey') && this.settings.get('mailgunDomain') && this.settings.get('mailgunBaseUrl')); - } - - @action - async renderEmailPreview(iframe) { - this._previewIframe = iframe; - - await this._fetchEmailData(); - // avoid timing issues when _fetchEmailData didn't perform any async ops - await timeout(100); - - if (iframe) { - iframe.contentWindow.document.open(); - iframe.contentWindow.document.write(this.html); - iframe.contentWindow.document.close(); - } - } - - @action - changeMemberSegment(segment) { - this.memberSegment = segment; - - if (this._previewIframe) { - this.renderEmailPreview(this._previewIframe); - } - } - - @task({drop: true}) - *sendPreviewEmailTask() { - try { - const resourceId = this.args.post.id; - const testEmail = this.previewEmailAddress.trim(); - - if (!validator.isEmail(testEmail)) { - this.sendPreviewEmailError = 'Please enter a valid email'; - return false; - } - if (!this.mailgunIsEnabled) { - this.sendPreviewEmailError = 'Please verify your email settings'; - return false; - } - this.sendPreviewEmailError = ''; - - const url = this.ghostPaths.url.api('/email_previews/posts', resourceId); - const data = {emails: [testEmail], memberSegment: this.memberSegment}; - const options = { - data, - dataType: 'json' - }; - - yield this.ajax.post(url, options); - return true; - } catch (error) { - if (error) { - let message = 'Email could not be sent, verify mail settings'; - - // grab custom error message if present - if ( - error.payload && error.payload.errors - && error.payload.errors[0] && error.payload.errors[0].message) { - message = htmlSafe(error.payload.errors[0].message); - } - - this.sendPreviewEmailError = message; - throw error; - } - } - } - - async _fetchEmailData() { - let {html, subject, memberSegment} = this; - let {post} = this.args; - - // Fetch newsletter - if (!this.newsletter && post.newsletter) { - this.newsletter = post.newsletter; - } - - if (!this.newsletter) { - const newsletters = (await this.store.query('newsletter', {filter: 'status:active', limit: 1})).toArray(); - const defaultNewsletter = newsletters[0]; - this.newsletter = defaultNewsletter; - } - - if (html && subject && memberSegment === this._lastMemberSegment) { - return {html, subject}; - } - - this._lastMemberSegment = memberSegment; - - // model is an email - if (post.html && post.subject) { - html = post.html; - subject = post.subject; - // model is a post with an existing email - } else if (post.email) { - html = post.email.html; - subject = post.email.subject; - // model is a post, fetch email preview - } else { - let url = new URL(this.ghostPaths.url.api('/email_previews/posts', post.id), window.location.href); - url.searchParams.set('memberSegment', this.memberSegment); - - let response = await this.ajax.request(url.href); - let [emailPreview] = response.email_previews; - html = emailPreview.html; - subject = emailPreview.subject; - } - - // inject extra CSS into the html for disabling links and scrollbars etc - let domParser = new DOMParser(); - let htmlDoc = domParser.parseFromString(html, 'text/html'); - let stylesheet = htmlDoc.querySelector('style'); - let originalCss = stylesheet.innerHTML; - stylesheet.innerHTML = `${originalCss}\n\n${INJECTED_CSS}`; - - const doctype = new XMLSerializer().serializeToString(htmlDoc.doctype); - html = doctype + htmlDoc.documentElement.outerHTML; - - this.html = html; - this.subject = subject; - } -} diff --git a/ghost/admin/app/components/modals/post-preview/mobile.hbs b/ghost/admin/app/components/modals/post-preview/mobile.hbs deleted file mode 100644 index bea3134074..0000000000 --- a/ghost/admin/app/components/modals/post-preview/mobile.hbs +++ /dev/null @@ -1,27 +0,0 @@ - -
- Share preview privately -
- {{@post.previewUrl}} -
- -
- - Open in new tab {{svg-jar "external"}} - -
-
\ No newline at end of file diff --git a/ghost/admin/app/components/modals/post-preview/mobile.js b/ghost/admin/app/components/modals/post-preview/mobile.js deleted file mode 100644 index 7aeb506727..0000000000 --- a/ghost/admin/app/components/modals/post-preview/mobile.js +++ /dev/null @@ -1,11 +0,0 @@ -import Component from '@glimmer/component'; -import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard'; -import {task, timeout} from 'ember-concurrency'; - -export default class ModalPostPreviewBrowserComponent extends Component { - @task - *copyPreviewUrl() { - copyTextToClipboard(this.args.post.previewUrl); - yield timeout(this.isTesting ? 50 : 3000); - } -} diff --git a/ghost/admin/app/components/modals/post-preview/social.hbs b/ghost/admin/app/components/modals/post-preview/social.hbs deleted file mode 100644 index 407242ac36..0000000000 --- a/ghost/admin/app/components/modals/post-preview/social.hbs +++ /dev/null @@ -1,258 +0,0 @@ - -
-

This is how your content will look when shared, you can click on any elements you’d like to edit.

-
-
-
-
- {{svg-jar "social-facebook" class="social-icon"}} -
-
{{or this.settings.metaTitle this.settings.title}}
-
12 hrs
-
-
-
- - - -
-
- {{#if (and this.facebookHovered (not this.facebookImage))}} - {{!-- only shown on hover when there's no image or fallback --}} - - {{/if}} - - - {{#each uploader.errors as |error|}} -
{{or error.context error.message}}
- {{/each}} - - {{#if (or this.facebookImage uploader.isUploading)}} -
-
- {{#if (or this.facebookHovered uploader.isUploading)}} - {{#if uploader.isUploading}} - {{uploader.progressBar}} - {{else}} - - {{/if}} - {{/if}} - - {{#if (and this.facebookHovered @post.ogImage)}} - - {{/if}} -
-
- {{/if}} - -
- -
-
- -
- {{!-- Ensures description is hidden if title exceeds one line --}} -
-
- {{this.config.blogDomain}} -
- {{#if this.editingFacebookTitle}} - - {{else}} -
- {{truncate this.facebookTitle}} -
- {{/if}} - {{#if this.editingFacebookDescription}} - - {{else}} -
- {{truncate this.facebookDescription}} -
- {{/if}} -
-
-
-
- {{svg-jar "facebook-like" class="z-999"}}{{svg-jar "facebook-heart" class="nl1"}}182 - 7 comments - 2 shares -
-
- - -
- -
- {{svg-jar "google"}} -
- - - {{#if this.editingMetaTitle}} - - {{else}} -
- {{this.serpTitle}} -
- {{/if}} - {{#if this.editingMetaDescription}} - - {{else}} -
- {{moment-format (now) "DD MMM YYYY"}} — {{truncate this.serpDescription 149}} -
- {{/if}} -
-
-
-
\ No newline at end of file diff --git a/ghost/admin/app/components/modals/post-preview/social.js b/ghost/admin/app/components/modals/post-preview/social.js deleted file mode 100644 index aec87454de..0000000000 --- a/ghost/admin/app/components/modals/post-preview/social.js +++ /dev/null @@ -1,209 +0,0 @@ -import Component from '@glimmer/component'; -import { - IMAGE_EXTENSIONS, - IMAGE_MIME_TYPES -} from 'ghost-admin/components/gh-image-uploader'; -import {action} from '@ember/object'; -import {inject as service} from '@ember/service'; -import {tracked} from '@glimmer/tracking'; -export default class ModalPostPreviewSocialComponent extends Component { - @service config; - @service settings; - @service ghostPaths; - - @tracked editingFacebookTitle = false; - @tracked editingFacebookDescription = false; - @tracked editingTwitterTitle = false; - @tracked editingTwitterDescription = false; - @tracked editingMetaTitle = false; - @tracked editingMetaDescription = false; - - imageExtensions = IMAGE_EXTENSIONS; - imageMimeTypes = IMAGE_MIME_TYPES; - - get _fallbackDescription() { - return this.args.post.customExcerpt || - this.serpDescription || - this.settings.get('description'); - } - - @action - blurElement(event) { - if (!event.shiftKey) { - event.preventDefault(); - event.target.blur(); - } - } - - @action - triggerFileDialog(name) { - const input = document.querySelector(`#${name}FileInput input`); - if (input) { - input.click(); - } - } - - // SERP - - get serpTitle() { - return this.args.post.metaTitle || this.args.post.title || '(Untitled)'; - } - - get serpURL() { - const urlParts = []; - - if (this.args.post.canonicalUrl) { - const canonicalUrl = new URL(this.args.post.canonicalUrl); - urlParts.push(canonicalUrl.host); - urlParts.push(...canonicalUrl.pathname.split('/').reject(p => !p)); - } else { - const blogUrl = new URL(this.config.get('blogUrl')); - urlParts.push(blogUrl.host); - urlParts.push(...blogUrl.pathname.split('/').reject(p => !p)); - urlParts.push(this.args.post.slug); - } - - return urlParts.join(' > '); - } - - get serpDescription() { - return this.args.post.metaDescription || this.args.post.excerpt; - } - - @action - editMetaTitle() { - this.editingMetaTitle = true; - } - - @action - setMetaTitle(event) { - const title = event.target.value; - this.args.post.metaTitle = title.trim(); - this.args.post.save(); - this.editingMetaTitle = false; - } - - @action - editMetaDescription() { - this.editingMetaDescription = true; - } - - @action - setMetaDescription(event) { - const description = event.target.value; - this.args.post.metaDescription = description.trim(); - this.args.post.save(); - this.editingMetaDescription = false; - } - - // Facebook - - get facebookTitle() { - return this.args.post.ogTitle || this.serpTitle; - } - - get facebookDescription() { - return this.args.post.ogDescription || this._fallbackDescription; - } - - get facebookImage() { - return this.args.post.ogImage || this.args.post.featureImage || this.settings.get('ogImage') || this.settings.get('coverImage'); - } - - @action - editFacebookTitle() { - this.editingFacebookTitle = true; - } - - @action - cancelEdit(property, event) { - event.preventDefault(); - event.target.value = this.args.post[property]; - event.target.blur(); - } - - @action - setFacebookTitle(event) { - const title = event.target.value; - this.args.post.ogTitle = title.trim(); - this.args.post.save(); - this.editingFacebookTitle = false; - } - - @action - editFacebookDescription() { - this.editingFacebookDescription = true; - } - - @action - setFacebookDescription() { - const description = event.target.value; - this.args.post.ogDescription = description.trim(); - this.args.post.save(); - this.editingFacebookDescription = false; - } - - @action - setFacebookImage([image]) { - this.args.post.ogImage = image.url; - this.args.post.save(); - } - - @action - clearFacebookImage() { - this.args.post.ogImage = null; - this.args.post.save(); - } - - // Twitter - - get twitterTitle() { - return this.args.post.twitterTitle || this.serpTitle; - } - - get twitterDescription() { - return this.args.post.twitterDescription || this._fallbackDescription; - } - - get twitterImage() { - return this.args.post.twitterImage || this.args.post.featureImage || this.settings.get('twitterImage') || this.settings.get('coverImage'); - } - - @action - editTwitterTitle() { - this.editingTwitterTitle = true; - } - - @action - setTwitterTitle(event) { - const title = event.target.value; - this.args.post.twitterTitle = title.trim(); - this.args.post.save(); - this.editingTwitterTitle = false; - } - - @action - editTwitterDescription() { - this.editingTwitterDescription = true; - } - - @action - setTwitterDescription() { - const description = event.target.value; - this.args.post.twitterDescription = description.trim(); - this.args.post.save(); - this.editingTwitterDescription = false; - } - - @action - setTwitterImage([image]) { - this.args.post.twitterImage = image.url; - this.args.post.save(); - } - - @action - clearTwitterImage() { - this.args.post.twitterImage = null; - this.args.post.save(); - } -} diff --git a/ghost/admin/app/controllers/editor.js b/ghost/admin/app/controllers/editor.js index 617e9b9468..2e9a92faf7 100644 --- a/ghost/admin/app/controllers/editor.js +++ b/ghost/admin/app/controllers/editor.js @@ -2,7 +2,6 @@ import ConfirmEditorLeaveModal from '../components/modals/editor/confirm-leave'; import Controller, {inject as controller} from '@ember/controller'; import DeletePostModal from '../components/modals/delete-post'; import PostModel from 'ghost-admin/models/post'; -import PostPreviewModal from '../components/modals/post-preview'; import boundOneWay from 'ghost-admin/utils/bound-one-way'; import classic from 'ember-classic-decorator'; import config from 'ghost-admin/config/environment'; @@ -109,7 +108,6 @@ export default class EditorController extends Controller { shouldFocusTitle = false; showReAuthenticateModal = false; - showPostPreviewModal = false; showUpgradeModal = false; showDeleteSnippetModal = false; showSettingsMenu = false; @@ -260,27 +258,6 @@ export default class EditorController extends Controller { } } - @action - openPostPreview(keyboardEvent) { - keyboardEvent?.preventDefault(); - - if (this.post.isDraft) { - this.openPostPreviewModal(); - } else { - window.open(this.post.previewUrl, '_blank', 'noopener'); - } - } - - @action - openPostPreviewModal() { - this.modals.open(PostPreviewModal, { - post: this.post, - saveTask: this.saveTask, - hasDirtyAttributes: this.hasDirtyAttributes, - setEditorSaveType: this.setSaveType - }); - } - @action toggleReAuthenticateModal() { if (this.showReAuthenticateModal) { @@ -485,16 +462,6 @@ export default class EditorController extends Controller { status = 'draft'; } } - - // let the adapter know it should use the `?email_recipient_filter` QP when saving - 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, - newsletter: options.newsletter - }); - } } // set manually here instead of in beforeSaveTask because the @@ -509,7 +476,6 @@ export default class EditorController extends Controller { post.set('statusScratch', null); if (!options.silent) { - this.set('showPostPreviewModal', false); this._showSaveNotification(prevStatus, post.get('status'), isNew ? true : false); } @@ -940,7 +906,6 @@ export default class EditorController extends Controller { this.set('post', null); this.set('hasDirtyAttributes', false); this.set('shouldFocusTitle', false); - this.set('showPostPreviewModal', false); this.set('showSettingsMenu', false); this.set('wordCount', null); diff --git a/ghost/admin/app/services/feature.js b/ghost/admin/app/services/feature.js index ead6b95166..47e7ef6a06 100644 --- a/ghost/admin/app/services/feature.js +++ b/ghost/admin/app/services/feature.js @@ -66,7 +66,6 @@ export default class FeatureService extends Service { @feature('improvedOnboarding') improvedOnboarding; @feature('membersTableStatus') membersTableStatus; @feature('selectablePortalLinks') selectablePortalLinks; - @feature('publishingFlow') publishingFlow; _user = null; diff --git a/ghost/admin/app/styles/app-dark.css b/ghost/admin/app/styles/app-dark.css index 53a599841b..1dc461a181 100644 --- a/ghost/admin/app/styles/app-dark.css +++ b/ghost/admin/app/styles/app-dark.css @@ -406,10 +406,6 @@ input:focus, color: var(--darkgrey); } -.gh-publishmenu-dropdown { - background: var(--whitegrey); -} - .gh-publish-setting { border-bottom: 1px solid var(--lightgrey-l1); } @@ -837,7 +833,7 @@ input:focus, background: var(--white) !important; } -.gh-btn-editor.active, +.gh-btn-editor.active, .gh-btn-editor:hover { background: var(--lightgrey) !important; } diff --git a/ghost/admin/app/styles/components/publishmenu.css b/ghost/admin/app/styles/components/publishmenu.css index 9e51b6b741..aed1a63109 100644 --- a/ghost/admin/app/styles/components/publishmenu.css +++ b/ghost/admin/app/styles/components/publishmenu.css @@ -20,189 +20,6 @@ outline: 0; } - -.gh-publishmenu { - position: relative; - z-index: 1000; - display: inherit; - margin-right: 8px; -} - -@media (max-width: 500px) { - .gh-publishmenu { - margin-right: 0; - } -} - -.gh-publishmenu .sent { - display: block; - height: 34px; - margin-top: -2px; - color: var(--darkgrey); - font-size: 1.35rem; - font-weight: 500; - line-height: 34px; - letter-spacing: .2px; -} - -.gh-publishmenu-dropdown { - position: absolute; - top: 100%; - right: 0; - margin: 5px 0 20px 0; - padding: 0px; - width: 344px; - background-color: #fff; - background-clip: padding-box; - border-radius: 4px; - list-style: none; - text-align: left; - text-transform: none; - font-size: 1.4rem; - font-weight: normal; - will-change: transform, opacity; - z-index: 99999; /* needs to sit on top of preview modal */ - /* box-shadow: 0 0 0 1px rgba(99,114,130,0.06), 0 8px 16px rgba(27,39,51,0.08); */ - box-shadow: var(--box-shadow-m); -} - -.gh-publishmenu-dropdown.ember-basic-dropdown--transitioning-in { - animation: fade-in-scale 0.2s; - animation-fill-mode: forwards; -} - -.gh-publishmenu-dropdown.ember-basic-dropdown--transitioning-out { - animation: fade-out 0.5s; - animation-fill-mode: forwards; -} - -.gh-publishmenu-heading { - margin: 0 0 15px 0; - padding: 20px 20px 0; - font-size: 1.7rem; - font-weight: 400; - line-height: 1.25em; -} - -.gh-publishmenu-select { - display: inline-block; -} - -.gh-publishmenu-select .ember-power-select-inline { - padding-right: 3px; - color: var(--black); - font-size: 1.7rem; - font-weight: 500; - line-height: 1.25em; -} - -.gh-publishmenu-select .ember-power-select-inline svg { - width: 9px !important; - height: 5.6px !important; - margin: 0 !important; -} - -.gh-publishmenu-select .ember-power-select-inline svg path { - stroke: var(--black) !important; - stroke-width: 4; -} - -.gh-publishmenu-select-dropdown { - width: unset !important; - min-width: min-content !important; - margin-top: 4px; - border-top: 1px solid var(--input-border-color) !important; - font-size: 1.4rem; - white-space: nowrap; - border-radius: 3px !important; -} - -.gh-publishmenu-content:not(.gh-publishmenu-content.no-border) { - border-bottom: var(--whitegrey) 1px solid; -} - -.gh-publishmenu-footer { - margin: 15px 0 0 0; - padding: 0 20px 20px; - display: flex; - align-items: center; - justify-content: flex-end; -} - -.gh-publishmenu-button { - float: right; - margin-left: 8px; -} - -.gh-publishmenu-radio { - display: flex; - margin: 20px 0; -} - -.gh-publishmenu-section { - padding: 0 20px; - border-top: var(--whitegrey) 1px solid; -} - -.gh-publishmenu-section.no-border { - border-top: 0; -} - -.gh-publishmenu-radio-button { - flex-shrink: 0; - position: relative; - width: 15px; - height: 15px; - border: color-mod(var(--whitegrey) l(-10%)) 1px solid; - border-radius: 100%; - background: #fff; -} - -.gh-publishmenu-radio-content { - display: flex; - flex-direction: column; - margin: 0 0 0 15px; - width: 100%; -} - -.gh-publishmenu-radio-label { - display: block; - font-size: 1.4rem; - line-height: 1.2em; - font-weight: 500; -} - -.gh-publishmenu-radio-desc { - font-size: 1.3rem; - line-height: 1.4em; - font-weight: 300; - color: var(--midgrey-l1); - margin-top: 2px; -} - -.gh-publishmenu-radio-label:hover, -.gh-publishmenu-radio-button:hover { - cursor: pointer; -} - -.gh-publishmenu-radio.active .gh-publishmenu-radio-button { - border-color: var(--black); - background: var(--black); -} - -.gh-publishmenu-radio.active .gh-publishmenu-radio-button:before { - display: block; - content: ""; - position: absolute; - top: 3px; - left: 3px; - width: 7px; - height: 7px; - background: var(--white); - border-radius: 100%; - box-shadow: rgba(0,0,0,0.25) 0 1px 3px; -} - .gh-date-time-picker { position: relative; display: flex; @@ -290,47 +107,6 @@ color: var(--red); } -.gh-publishmenu-email { - margin: 15px 0; - justify-content: space-between; - align-items: center; -} - -.gh-publishmenu-email .gh-box { - padding: 12px 16px; - font-size: 1.3rem; - line-height: 1.5em; -} - -.gh-publishmenu-email .select-members { - margin-bottom: .2rem; -} - -.gh-publishmenu-email .segment-totals { - color: var(--midgrey-l1); - font-size: 1.3rem; - font-weight: 300; -} - -.for-checkbox .gh-publishmenu-email-checkbox { - margin-right: 0; - margin-top: -2px; - background: var(--white); -} - -.gh-publishmenu-email-label.disabled { - pointer-events: none; -} - -.gh-publishmenu-content .for-switch.pe-none { - opacity: 0.6; -} - -.gh-publishmenu-email-info { - margin: 15px 0; - color: var(--midgrey); -} - .gh-publish-send-to { display: flex; } @@ -412,30 +188,6 @@ font-size: 1.3rem; } -.gh-publishmenu-checkbox-disabled { - color: var(--midlightgrey); - opacity: 0.6; - pointer-events: none; -} - -.gh-publishmenu-checkbox-disabled p { - color: var(--midgrey) !important; -} - -.gh-publishmenu-newsletter-trigger { - padding: 7px 10px; -} - -.gh-publishmenu-newsletter-trigger.disabled span { - opacity: .5; -} - -.gh-publishmenu-newsletter-trigger svg { - position: absolute; - top: 50%; - right: 10px; -} - .gh-publish-newsletter-dropdown { z-index: 99999; } diff --git a/ghost/admin/app/styles/layouts/settings.css b/ghost/admin/app/styles/layouts/settings.css index 8d545be712..1ae399c145 100644 --- a/ghost/admin/app/styles/layouts/settings.css +++ b/ghost/admin/app/styles/layouts/settings.css @@ -813,63 +813,6 @@ height: 6px; } -.gh-email-publishmenu-preview .gh-main-section-header.small { - padding-top: 4px; -} - -.gh-email-publishmenu-preview .gh-publishmenu-dropdown-container { - height: calc(346px * .9); -} - -.gh-email-publishmenu-preview .gh-publishmenu-dropdown { - position: relative; - top: inherit; - right: inherit; - margin: 0; - box-shadow: - 0 7px 7px rgba(0, 0, 0, 0.04), - 0 3px 2.2px -5px rgba(0, 0, 0, 0.011), - 0 6px 5.3px -5px rgba(0, 0, 0, 0.016), - 0 10.5px 10px -5px rgba(0, 0, 0, 0.02), - 0 18px 17.9px -5px rgba(0, 0, 0, 0.024), - 0 33.8px 33.4px -5px rgba(0, 0, 0, 0.029), - 0 80px 80px -5px rgba(0, 0, 0, 0.04) - ; - transform: scale(.9); - transform-origin: top left; - pointer-events: none; -} - -.gh-publishmenu-heading-dropdown { - font-weight: 500; - color: var(--black); -} - -.gh-publishmenu-heading-dropdown svg { - width: auto; - height: 9px; - margin-right: 4px; -} - -.gh-publishmenu-heading-dropdown svg path { - stroke: var(--black); - stroke-width: 2px; -} - -.gh-email-publishmenu-preview .gh-publishmenu-radio .gh-publishmenu-radio-label:after { - content: ""; - display: block; - width: 80%; - height: 12px; - margin-top: 8px; - background: var(--whitegrey-d1); - border-radius: 6px; -} - -.gh-email-publishmenu-preview .gh-publishmenu-radio.active .gh-publishmenu-radio-label:after { - width: 100%; -} - .member-segments .select-members { margin-bottom: .4rem; } @@ -966,9 +909,9 @@ body:not([data-user-is-dragging]) .gh-newsletter-card-draggable:hover .grab-news visibility: visible; opacity: 1; - /* - To make sure the grab handler also fades out correctly and only change visibility after the animation, - we need to update the animation curve for visibility to step-start at the start of the animation + /* + To make sure the grab handler also fades out correctly and only change visibility after the animation, + we need to update the animation curve for visibility to step-start at the start of the animation */ transition: visibility 200ms step-start, opacity 200ms ease-in-out; } @@ -1115,7 +1058,7 @@ body:not([data-user-is-dragging]) .gh-newsletter-card-draggable:hover .grab-news .gh-newsletters-labs .gh-members-emailpreview-faux .strong { color: var(--darkgrey); font-size: 1.4rem; - font-weight: 600; + font-weight: 600; } .gh-newsletters-labs .gh-members-emailpreview-faux p { diff --git a/ghost/admin/app/templates/settings/labs.hbs b/ghost/admin/app/templates/settings/labs.hbs index 23d648ea7c..16efa911b0 100644 --- a/ghost/admin/app/templates/settings/labs.hbs +++ b/ghost/admin/app/templates/settings/labs.hbs @@ -291,19 +291,6 @@ -
-
-
-

Publishing flow

-

- Revised workflow experience when publishing/scheduling/sending -

-
-
- -
-
-
{{/if}}