2017-08-22 10:53:26 +03:00
import Component from '@ember/component' ;
2019-11-21 02:23:23 +03:00
import EmailFailedError from 'ghost-admin/errors/email-failed-error' ;
2022-01-21 22:25:47 +03:00
import { bind , schedule } from '@ember/runloop' ;
2017-08-22 10:53:26 +03:00
import { computed } from '@ember/object' ;
2020-11-23 15:20:26 +03:00
import { or , reads } from '@ember/object/computed' ;
2017-10-30 12:38:01 +03:00
import { inject as service } from '@ember/service' ;
2019-11-21 02:23:23 +03:00
import { task , timeout } from 'ember-concurrency' ;
const CONFIRM _EMAIL _POLL _LENGTH = 1000 ;
2019-11-22 18:09:48 +03:00
const CONFIRM _EMAIL _MAX _POLL _LENGTH = 15 * 1000 ;
2017-04-11 16:39:45 +03:00
export default Component . extend ( {
2017-10-30 12:38:01 +03:00
clock : service ( ) ,
2020-11-06 21:54:27 +03:00
config : service ( ) ,
2021-12-07 03:18:13 +03:00
feature : service ( ) ,
limit : service ( ) ,
modals : service ( ) ,
2020-11-11 20:08:43 +03:00
session : service ( ) ,
2021-12-07 03:18:13 +03:00
settings : service ( ) ,
2020-11-11 20:08:43 +03:00
store : service ( ) ,
2017-04-11 16:39:45 +03:00
classNames : 'gh-publishmenu' ,
2018-03-27 20:06:55 +03:00
displayState : 'draft' ,
2022-01-22 03:05:05 +03:00
saveType : 'publish' ,
2017-04-11 16:39:45 +03:00
post : null ,
2018-03-27 20:06:55 +03:00
postStatus : 'draft' ,
2021-08-26 22:01:26 +03:00
distributionAction : 'publish_send' ,
2018-01-11 20:43:23 +03:00
runningText : null ,
2020-01-10 17:25:59 +03:00
saveTask : null ,
2021-05-07 13:58:05 +03:00
sendEmailWhenPublished : null ,
2020-01-10 17:25:59 +03:00
typedDateError : null ,
2021-05-04 20:46:22 +03:00
isSendingEmailLimited : false ,
sendingEmailLimitError : '' ,
2017-04-11 16:39:45 +03:00
_publishedAtBlogTZ : null ,
2018-01-11 20:43:23 +03:00
_previousStatus : null ,
2017-04-11 16:39:45 +03:00
2017-05-23 16:30:00 +03:00
isClosing : null ,
2019-11-07 11:37:26 +03:00
onClose ( ) { } ,
2017-04-11 16:39:45 +03:00
forcePublishedMenu : reads ( 'post.pastScheduledTime' ) ,
2021-07-12 15:55:56 +03:00
hasEmailPermission : or ( 'session.user.isOwnerOnly' , 'session.user.isAdminOnly' , 'session.user.isEditor' ) ,
2020-11-23 15:20:26 +03:00
2021-10-15 18:45:46 +03:00
emailOnly : computed . equal ( 'distributionAction' , 'send' ) ,
2021-05-07 12:02:19 +03:00
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' ) ;
2021-01-25 12:23:03 +03:00
let isPost = this . post . isPost ;
2020-11-06 21:54:27 +03:00
let hasSentEmail = ! ! this . post . email ;
2021-05-07 12:02:19 +03:00
return this . hasEmailPermission &&
! isDisabled &&
mailgunIsConfigured &&
isPost &&
! hasSentEmail ;
} ) ,
2017-04-11 16:39:45 +03:00
postState : computed ( 'post.{isPublished,isScheduled}' , 'forcePublishedMenu' , function ( ) {
2019-03-06 16:53:54 +03:00
if ( this . forcePublishedMenu || this . get ( 'post.isPublished' ) ) {
2017-04-11 16:39:45 +03:00
return 'published' ;
} else if ( this . get ( 'post.isScheduled' ) ) {
return 'scheduled' ;
} else {
return 'draft' ;
}
} ) ,
triggerText : computed ( 'postState' , function ( ) {
2019-03-06 16:53:54 +03:00
let state = this . postState ;
2017-04-11 16:39:45 +03:00
if ( state === 'published' ) {
return 'Update' ;
} else if ( state === 'scheduled' ) {
return 'Scheduled' ;
} else {
return 'Publish' ;
}
} ) ,
2018-01-05 18:38:23 +03:00
_runningText : computed ( 'postState' , 'saveType' , function ( ) {
2019-03-06 16:53:54 +03:00
let saveType = this . saveType ;
let postState = this . postState ;
2017-07-20 13:46:31 +03:00
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' ;
} ) ,
2021-10-21 15:07:56 +03:00
buttonText : computed ( 'postState' , 'saveType' , 'distributionAction' , 'sendEmailWhenPublished' , function ( ) {
2019-03-06 16:53:54 +03:00
let saveType = this . saveType ;
let postState = this . postState ;
2021-10-21 15:07:56 +03:00
let distributionAction = this . distributionAction ;
2017-06-15 20:35:23 +03:00
let buttonText ;
if ( postState === 'draft' ) {
2021-08-26 22:01:26 +03:00
switch ( distributionAction ) {
case 'publish_send' :
2021-10-21 15:07:56 +03:00
if ( saveType === 'publish' ) {
buttonText = 'Publish' ;
if ( this . canSendEmail && this . sendEmailWhenPublished && this . sendEmailWhenPublished !== 'none' ) {
buttonText = ` ${ buttonText } & send ` ;
}
} else {
buttonText = 'Schedule' ;
}
2021-08-26 22:01:26 +03:00
break ;
case 'publish' :
buttonText = ( saveType === 'publish' ) ? 'Publish' : 'Schedule' ;
break ;
case 'send' :
2021-08-13 15:23:35 +03:00
buttonText = saveType === 'publish' ? 'Send' : 'Schedule' ;
2021-08-26 22:01:26 +03:00
break ;
2021-08-13 15:23:35 +03:00
}
2017-06-15 20:35:23 +03:00
}
if ( postState === 'published' ) {
2017-07-20 13:46:31 +03:00
buttonText = saveType === 'publish' ? 'Update' : 'Unpublish' ;
2017-06-15 20:35:23 +03:00
}
2017-04-11 16:39:45 +03:00
2017-06-15 20:35:23 +03:00
if ( postState === 'scheduled' ) {
2017-07-20 13:46:31 +03:00
buttonText = saveType === 'schedule' ? 'Reschedule' : 'Unschedule' ;
2017-06-15 20:35:23 +03:00
}
return buttonText || 'Publish' ;
} ) ,
2018-01-05 18:38:23 +03:00
successText : computed ( '_previousStatus' , 'postState' , function ( ) {
2019-03-06 16:53:54 +03:00
let postState = this . postState ;
let previousStatus = this . _previousStatus ;
2017-06-15 20:35:23 +03:00
let buttonText ;
if ( previousStatus === 'draft' ) {
buttonText = postState === 'published' ? 'Published' : 'Scheduled' ;
}
if ( previousStatus === 'published' ) {
2017-07-20 13:46:31 +03:00
buttonText = postState === 'draft' ? 'Unpublished' : 'Updated' ;
2017-06-15 20:35:23 +03:00
}
if ( previousStatus === 'scheduled' ) {
2017-07-20 13:46:31 +03:00
buttonText = postState === 'draft' ? 'Unscheduled' : 'Rescheduled' ;
2017-06-15 20:35:23 +03:00
}
return buttonText ;
2017-04-11 16:39:45 +03:00
} ) ,
2021-05-07 12:02:19 +03:00
defaultEmailRecipients : computed ( 'settings.{editorDefaultEmailRecipients,editorDefaultEmailRecipientsFilter}' , 'post.visibility' , function ( ) {
const defaultEmailRecipients = this . settings . get ( 'editorDefaultEmailRecipients' ) ;
2021-05-07 13:58:05 +03:00
if ( defaultEmailRecipients === 'disabled' ) {
return null ;
2021-05-07 12:02:19 +03:00
}
if ( defaultEmailRecipients === 'visibility' ) {
2021-05-11 15:20:53 +03:00
if ( this . post . visibility === 'public' ) {
2021-05-07 13:58:05 +03:00
return 'status:free,status:-free' ;
2021-05-07 12:02:19 +03:00
}
2021-05-21 19:53:29 +03:00
if ( this . post . visibility === 'members' ) {
return 'status:free,status:-free' ;
}
if ( this . post . visibility === 'paid' ) {
return 'status:-free' ;
}
2021-09-14 10:39:11 +03:00
if ( this . post . visibility === 'filter' ) {
return this . post . visibilityFilter ;
}
2021-05-11 15:20:53 +03:00
return this . post . visibility ;
2021-05-07 12:02:19 +03:00
}
2021-05-07 13:58:05 +03:00
return this . settings . get ( 'editorDefaultEmailRecipientsFilter' ) ;
2021-05-07 12:02:19 +03:00
} ) ,
2018-03-27 20:06:55 +03:00
didReceiveAttrs ( ) {
this . _super ( ... arguments ) ;
2022-01-22 03:05:05 +03:00
const updateSaveTypeForPostStatus = ( status ) => {
if ( status === 'draft' || status === 'published' ) {
this . set ( 'saveType' , 'publish' ) ;
}
if ( status === 'scheduled' ) {
this . set ( 'saveType' , 'schedule' ) ;
}
} ;
2018-03-27 20:06:55 +03:00
// 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
2019-03-06 16:53:54 +03:00
let postStatus = this . postStatus ;
2018-03-27 20:06:55 +03:00
if ( postStatus !== this . _postStatus ) {
if ( this . get ( 'saveTask.isRunning' ) ) {
this . get ( 'saveTask.last' ) . then ( ( ) => {
this . set ( 'displayState' , postStatus ) ;
2022-01-22 03:05:05 +03:00
updateSaveTypeForPostStatus ( postStatus ) ;
2018-03-27 20:06:55 +03:00
} ) ;
} else {
this . set ( 'displayState' , postStatus ) ;
2022-01-22 03:05:05 +03:00
updateSaveTypeForPostStatus ( postStatus ) ;
2018-03-27 20:06:55 +03:00
}
}
2019-03-06 16:53:54 +03:00
this . _postStatus = this . postStatus ;
2021-05-07 12:02:19 +03:00
this . setDefaultSendEmailWhenPublished ( ) ;
2021-08-20 13:06:58 +03:00
this . checkIsSendingEmailLimitedTask . perform ( ) ;
2021-10-12 13:32:33 +03:00
if ( this . post . isPage ) {
this . set ( 'distributionAction' , 'publish' ) ;
}
2018-03-27 20:06:55 +03:00
} ,
2017-04-11 16:39:45 +03:00
actions : {
setSaveType ( saveType ) {
2019-03-06 16:53:54 +03:00
let post = this . post ;
2017-04-11 16:39:45 +03:00
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' ) ;
}
} ,
2020-11-06 21:54:27 +03:00
setSendEmailWhenPublished ( sendEmailWhenPublished ) {
this . set ( 'sendEmailWhenPublished' , sendEmailWhenPublished ) ;
} ,
2021-08-26 22:01:26 +03:00
setDistributionAction ( distributionAction ) {
this . set ( 'distributionAction' , distributionAction ) ;
if ( distributionAction === 'publish' ) {
2021-10-15 17:42:32 +03:00
this . set ( 'sendEmailWhenPublished' , 'none' ) ;
2021-08-26 22:01:26 +03:00
} else {
this . set ( 'sendEmailWhenPublished' , this . defaultEmailRecipients ) ;
}
} ,
2017-04-11 16:39:45 +03:00
open ( ) {
this . _cachePublishedAtBlogTZ ( ) ;
2017-05-23 16:30:00 +03:00
this . set ( 'isClosing' , false ) ;
2017-04-11 16:39:45 +03:00
this . get ( 'post.errors' ) . clear ( ) ;
2021-05-07 12:02:19 +03:00
this . setDefaultSendEmailWhenPublished ( ) ;
2019-03-06 16:53:54 +03:00
if ( this . onOpen ) {
this . onOpen ( ) ;
2017-04-19 19:09:31 +03:00
}
2017-04-11 16:39:45 +03:00
} ,
close ( dropdown , e ) {
2019-11-07 11:37:26 +03:00
// 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 ;
}
2017-04-11 16:39:45 +03:00
}
2019-11-07 11:37:26 +03:00
if ( ! this . _skipDropdownCloseCleanup ) {
this . _cleanup ( ) ;
2017-04-19 19:09:31 +03:00
}
2019-11-07 11:37:26 +03:00
this . _skipDropdownCloseCleanup = false ;
2017-05-23 16:30:00 +03:00
2019-11-07 11:37:26 +03:00
this . onClose ( ) ;
2017-05-23 16:30:00 +03:00
this . set ( 'isClosing' , true ) ;
2017-04-11 16:39:45 +03:00
return true ;
2021-05-07 13:58:05 +03:00
} ,
updateMemberCount ( count ) {
this . memberCount = count ;
2021-12-01 14:50:51 +03:00
} ,
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 ( ) {
2021-12-07 03:18:13 +03:00
this . send ( 'setSaveType' , 'publish' ) ;
2021-12-01 14:50:51 +03:00
this . save . perform ( ) ;
} ) ;
2017-04-11 16:39:45 +03:00
}
2018-01-11 20:43:23 +03:00
} ,
2021-05-07 12:02:19 +03:00
setDefaultSendEmailWhenPublished ( ) {
2022-01-21 22:25:47 +03:00
if ( this . isSendingEmailLimited ) {
2021-08-19 16:50:21 +03:00
this . set ( 'sendEmailWhenPublished' , false ) ;
} else if ( this . postStatus === 'draft' && this . canSendEmail ) {
2021-05-07 12:02:19 +03:00
// Set default newsletter recipients
this . set ( 'sendEmailWhenPublished' , this . defaultEmailRecipients ) ;
} else {
this . set ( 'sendEmailWhenPublished' , this . post . emailRecipientFilter ) ;
2020-11-11 20:08:43 +03:00
}
2021-05-07 12:02:19 +03:00
} ,
2020-11-11 20:08:43 +03:00
2021-05-04 20:46:22 +03:00
checkIsSendingEmailLimitedTask : task ( function * ( ) {
try {
2021-08-20 13:06:58 +03:00
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 ;
}
2021-05-04 20:46:22 +03:00
this . set ( 'isSendingEmailLimited' , false ) ;
this . set ( 'sendingEmailLimitError' , null ) ;
} catch ( error ) {
this . set ( 'isSendingEmailLimited' , true ) ;
this . set ( 'sendingEmailLimitError' , error . message ) ;
this . set ( 'sendEmailWhenPublished' , 'none' ) ;
}
} ) ,
2021-12-07 03:18:13 +03:00
reloadSettingsTask : task ( function * ( ) {
yield this . settings . reload ( ) ;
2019-11-07 11:37:26 +03:00
} ) ,
2021-12-07 03:18:13 +03:00
save : task ( function * ( options = { } ) {
const { post , saveType } = this ;
2019-11-21 02:23:23 +03:00
2021-12-07 03:18:13 +03:00
// don't allow save if an invalid schedule date is present
if ( this . typedDateError ) {
return false ;
2019-11-21 02:23:23 +03:00
}
2021-12-07 03:18:13 +03:00
// 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 ;
}
2019-11-21 02:23:23 +03:00
2021-12-07 03:18:13 +03:00
// always opens publish confirmation if post will be published/scheduled
if ( post . status === 'draft' && ( saveType === 'publish' || saveType === 'schedule' ) ) {
if ( options . dropdown ) {
this . _skipDropdownCloseCleanup = true ;
options . dropdown . actions . close ( ) ;
2019-11-21 02:23:23 +03:00
}
2021-12-07 03:18:13 +03:00
return yield this . modals . open ( 'modals/editor/confirm-publish' , {
post : this . post ,
emailOnly : this . emailOnly ,
sendEmailWhenPublished : this . sendEmailWhenPublished ,
isScheduled : saveType === 'schedule' ,
confirm : this . saveWithConfirmedPublish . perform ,
retryEmailSend : this . retryEmailSendTask . perform
} , {
beforeClose : bind ( this , this . _cleanup )
} ) ;
2019-11-21 02:23:23 +03:00
}
2021-12-07 03:18:13 +03:00
return yield this . _saveTask . perform ( options ) ;
2019-11-07 11:37:26 +03:00
} ) ,
2021-12-07 03:18:13 +03:00
saveWithConfirmedPublish : task ( function * ( ) {
return yield this . _saveTask . perform ( ) ;
2019-11-22 18:09:48 +03:00
} ) ,
2021-12-07 03:18:13 +03:00
retryEmailSendTask : task ( function * ( ) {
2019-11-22 18:09:48 +03:00
if ( ! this . post . email ) {
return ;
}
let email = yield this . post . email . retry ( ) ;
let pollTimeout = 0 ;
if ( email && email . status !== 'submitted' ) {
2021-12-07 03:18:13 +03:00
while ( pollTimeout < CONFIRM _EMAIL _MAX _POLL _LENGTH ) {
2019-11-22 18:09:48 +03:00
yield timeout ( CONFIRM _EMAIL _POLL _LENGTH ) ;
2021-12-07 03:18:13 +03:00
pollTimeout += CONFIRM _EMAIL _POLL _LENGTH ;
2019-11-22 18:09:48 +03:00
email = yield email . reload ( ) ;
if ( email . status === 'submitted' ) {
break ;
}
if ( email . status === 'failed' ) {
throw new EmailFailedError ( email . error ) ;
}
}
}
return email ;
} ) ,
2021-12-07 03:18:13 +03:00
_saveTask : task ( function * ( ) {
2020-01-10 17:25:59 +03:00
let {
post ,
2021-10-15 17:42:32 +03:00
emailOnly ,
2020-01-10 17:25:59 +03:00
sendEmailWhenPublished ,
2021-12-07 03:18:13 +03:00
saveType
2020-01-10 17:25:59 +03:00
} = this ;
2018-01-11 20:43:23 +03:00
// runningText needs to be declared before the other states change during the
// save action.
2019-03-06 16:53:54 +03:00
this . set ( 'runningText' , this . _runningText ) ;
2018-01-11 20:43:23 +03:00
this . set ( '_previousStatus' , this . get ( 'post.status' ) ) ;
2019-11-14 20:33:35 +03:00
this . setSaveType ( saveType ) ;
2019-11-04 13:25:24 +03:00
2018-01-11 20:43:23 +03:00
try {
2020-04-23 21:43:10 +03:00
// will show alert for non-date related failed validations
2021-10-15 17:42:32 +03:00
post = yield this . saveTask . perform ( { sendEmailWhenPublished , emailOnly } ) ;
2019-11-14 20:33:35 +03:00
2018-01-11 20:43:23 +03:00
this . _cachePublishedAtBlogTZ ( ) ;
2021-12-07 03:18:13 +03:00
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 ( ) ;
2018-01-11 20:43:23 +03:00
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' ) ;
} ,
2019-11-07 11:37:26 +03:00
_cleanup ( ) {
2021-10-15 17:42:32 +03:00
this . set ( 'distributionAction' , 'publish_send' ) ;
2019-11-07 11:37:26 +03:00
// 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
2019-03-06 16:53:54 +03:00
this . post . set ( 'publishedAtBlogTZ' , this . _publishedAtBlogTZ ) ;
2019-11-07 11:37:26 +03:00
this . post . set ( 'statusScratch' , null ) ;
this . post . validate ( ) ;
2017-04-11 16:39:45 +03:00
}
} ) ;