diff --git a/assets/css/src/admin.styl b/assets/css/src/admin.styl index cbc530bd5e..b037c74fba 100644 --- a/assets/css/src/admin.styl +++ b/assets/css/src/admin.styl @@ -1,6 +1,7 @@ @import 'nib' @require 'select2/dist/css/select2.css' +@require 'datepicker' @require 'common' @require 'modal' @@ -18,4 +19,4 @@ @require 'settings' @require 'progress_bar' -@require 'subscribers' \ No newline at end of file +@require 'subscribers' diff --git a/assets/css/src/datepicker.styl b/assets/css/src/datepicker.styl new file mode 100644 index 0000000000..9f8e358c82 --- /dev/null +++ b/assets/css/src/datepicker.styl @@ -0,0 +1,97 @@ +/* + Taken from: https://github.com/rtsinani/jquery-datepicker-skins + Skin: Melon +*/ + +.ui-widget + font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; + background: #2e3641; + border: none; + border-radius: 0; + -webkit-border-radius: 0; + -moz-border-radius: 0; + +.ui-datepicker + padding: 0; + +.ui-datepicker-header + border: none; + background: transparent; + font-weight: normal; + font-size: 15px; + +.ui-datepicker-header .ui-state-hover + background: transparent; + border-color: transparent; + cursor: pointer; + border-radius: 0; + -webkit-border-radius: 0; + -moz-border-radius: 0; + +.ui-datepicker .ui-datepicker-title + margin-top: .4em; + margin-bottom: .3em; + color: #e9f0f4; + +.ui-datepicker .ui-datepicker-prev-hover +.ui-datepicker .ui-datepicker-next-hover +.ui-datepicker .ui-datepicker-next +.ui-datepicker .ui-datepicker-prev + top: .9em; + border:none; + +.ui-datepicker .ui-datepicker-prev-hover + left: 2px; + +.ui-datepicker .ui-datepicker-next-hover + right: 2px; + +.ui-datepicker .ui-datepicker-next span +.ui-datepicker .ui-datepicker-prev span + background-image: url(../img/datepicker/ui-icons_ffffff_256x240.png); + background-position: -32px 0; + margin-top: 0; + top: 0; + font-weight: normal; + + +.ui-datepicker .ui-datepicker-prev span + background-position: -96px 0; + + +.ui-datepicker table + margin: 0; + +.ui-datepicker th + padding: 1em 0; + color: #ccc; + font-size: 13px; + font-weight: normal; + border: none; + border-top: 1px solid #3a414d; + +.ui-datepicker td + background: #f97e76; + border: none; + padding: 0; + + +td .ui-state-default + background: transparent; + border: none; + text-align: center; + padding: .5em; + margin: 0; + font-weight: normal; + color: #efefef; + font-size: 16px; + +.ui-state-disabled + opacity: 1; + +.ui-state-disabled .ui-state-default + color: #fba49e; + +td .ui-state-active +td .ui-state-hover + background: #2e3641; diff --git a/assets/img/datepicker/ui-icons_ffffff_256x240.png b/assets/img/datepicker/ui-icons_ffffff_256x240.png new file mode 100644 index 0000000000..4f624bb2b1 Binary files /dev/null and b/assets/img/datepicker/ui-icons_ffffff_256x240.png differ diff --git a/assets/js/src/newsletters/send.jsx b/assets/js/src/newsletters/send.jsx index 52b3eaba9a..f75b4df096 100644 --- a/assets/js/src/newsletters/send.jsx +++ b/assets/js/src/newsletters/send.jsx @@ -35,9 +35,9 @@ define( }, getFieldsByNewsletter: function(newsletter) { switch(newsletter.type) { - case 'notification': return NotificationNewsletterFields; - case 'welcome': return WelcomeNewsletterFields; - default: return StandardNewsletterFields; + case 'notification': return NotificationNewsletterFields(newsletter); + case 'welcome': return WelcomeNewsletterFields(newsletter); + default: return StandardNewsletterFields(newsletter); } }, isAutomatedNewsletter: function() { diff --git a/assets/js/src/newsletters/send/standard.jsx b/assets/js/src/newsletters/send/standard.jsx index 2a03f8a3ad..8b87fbda62 100644 --- a/assets/js/src/newsletters/send/standard.jsx +++ b/assets/js/src/newsletters/send/standard.jsx @@ -1,12 +1,180 @@ define( [ - 'mailpoet' + 'react', + 'jquery', + 'mailpoet', + 'form/fields/checkbox.jsx', + 'form/fields/select.jsx', + 'form/fields/text.jsx', ], function( - MailPoet + React, + jQuery, + MailPoet, + Checkbox, + Select, + Text ) { - var settings = window.mailpoet_settings || {}; + var settings = window.mailpoet_settings || {}, + currentTime = window.mailpoet_current_time || '00:00', + timeOfDayItems = window.mailpoet_schedule_time_of_day; + + var isScheduledField = { + name: 'isScheduled', + }; + + var DateText = React.createClass({ + componentDidMount: function() { + var $element = jQuery(this.refs.dateInput); + if ($element.datepicker) { + $element.datepicker({ + dateFormat: "yy-mm-dd", + }); + } + }, + render: function() { + return ( + + ); + }, + }); + + var TimeSelect = React.createClass({ + render: function() { + const options = Object.keys(timeOfDayItems).map( + (value, index) => { + return ( + + ); + } + ); + + return ( + + ); + } + }); + + var DateTime = React.createClass({ + _DATE_TIME_SEPARATOR: " ", + getInitialState: function() { + return this._buildStateFromProps(this.props); + }, + componentWillReceiveProps: function(nextProps) { + this.setState(this._buildStateFromProps(nextProps)); + }, + _buildStateFromProps: function(props) { + const [date, time] = props.value.split(this._DATE_TIME_SEPARATOR) + return { + date: date, + time: time, + }; + }, + handleChange: function(event) { + var newState = {}; + newState[event.target.name] = event.target.value; + + this.setState(newState, function() { + this.propagateChange(); + }); + }, + propagateChange: function() { + if (this.props.onChange) { + this.props.onChange({ + target: { + name: this.props.name || '', + value: this.getDateTime(), + } + }) + } + }, + getDateTime: function() { + return [this.state.date, this.state.time].join(this._DATE_TIME_SEPARATOR); + }, + render: function() { + return ( + + + + + ); + } + }); + + var StandardScheduling = React.createClass({ + getInitialState: function() { + return { + isScheduled: '0', + scheduledAt: '2016-05-04 14:00:00', + }; + }, + handleChange: function(event) { + var newState = {}; + newState[event.target.name] = event.target.value; + this.setState(newState); + }, + handleCheckboxChange: function(event) { + event.target.value = this.refs.isScheduled.checked ? '1' : '0'; + this.handleChange(event); + }, + isScheduled: function() { + return this.state.isScheduled === '1'; + }, + render: function() { + var isChecked = this.isScheduled(), + schedulingOptions; + + if (isChecked) { + schedulingOptions = ( + + + + {MailPoet.I18n.t('localTimeIs')} {currentTime} + + + ); + } + + return ( +
+ + + {schedulingOptions} +
+ ); + }, + }); var fields = [ { @@ -84,9 +252,17 @@ define( placeholder: MailPoet.I18n.t('replyToAddressPlaceholder') } ] + }, + { + name: 'options', + label: MailPoet.I18n.t('scheduleIt'), + type: 'reactComponent', + component: StandardScheduling, } ]; - return fields; + return function(newsletter) { + return fields; + }; } ); diff --git a/assets/js/src/newsletters/send/welcome.jsx b/assets/js/src/newsletters/send/welcome.jsx index cb8940ceb8..e78ce2eba4 100644 --- a/assets/js/src/newsletters/send/welcome.jsx +++ b/assets/js/src/newsletters/send/welcome.jsx @@ -71,7 +71,9 @@ define( } ]; - return fields; + return function(newsletter) { + return fields; + }; } ); diff --git a/lib/Config/Menu.php b/lib/Config/Menu.php index dd2f3aaf85..c6507ab5d9 100644 --- a/lib/Config/Menu.php +++ b/lib/Config/Menu.php @@ -14,6 +14,7 @@ use MailPoet\Subscribers\ImportExport\BootStrapMenu; use MailPoet\Util\DKIM; use MailPoet\Util\Permissions; use MailPoet\Listing; +use MailPoet\WP\DateTime; if(!defined('ABSPATH')) exit; @@ -402,6 +403,18 @@ class Menu { $data['roles'] = $wp_roles->get_names(); $data['roles']['mailpoet_all'] = __('In any WordPress role'); + $date_time = new DateTime(); + $data['current_date'] = $date_time->getCurrentDate(DateTime::INTERNAL_DATE_FORMAT); + $data['current_time'] = $date_time->getCurrentTime(); + $data['schedule_time_of_day'] = $date_time->getTimeInterval( + '00:00:00', + '+1 hour', + 24 + ); + + wp_enqueue_script('jquery-ui'); + wp_enqueue_script('jquery-ui-datepicker'); + echo $this->renderer->render('newsletters.html', $data); } diff --git a/lib/Config/Populator.php b/lib/Config/Populator.php index db1f29b2c5..47729ee726 100644 --- a/lib/Config/Populator.php +++ b/lib/Config/Populator.php @@ -140,6 +140,14 @@ class Populator { function newsletter_option_fields() { return array( + array( + 'name' => 'isScheduled', + 'newsletter_type' => 'standard', + ), + array( + 'name' => 'scheduledAt', + 'newsletter_type' => 'standard', + ), array( 'name' => 'event', 'newsletter_type' => 'welcome', diff --git a/lib/WP/DateTime.php b/lib/WP/DateTime.php new file mode 100644 index 0000000000..b42bfbd6ec --- /dev/null +++ b/lib/WP/DateTime.php @@ -0,0 +1,65 @@ +getTimeFormat(); + return current_time($format); + } + + function getCurrentDate($format=false) { + if (empty($format)) $format = $this->getDateFormat(); + return $this->getCurrentTime($format); + } + + function getTime($time, $format=false) { + if (empty($format)) $format = $this->getTimeFormat(); + + return date($format, $time); + } + + /** + * Generates a list of time strings within an interval, + * formatted and mapped from INTERNAL_TIME_FORMAT to Wordpress time strings. + */ + function getTimeInterval( + $start_time='00:00:00', + $time_step='+1 hour', + $total_steps=24 + ) { + $steps = array(); + + $internal_time = $start_time; + $timestamp = strtotime($internal_time); + + for ($step = 0; $step < $total_steps; $step += 1) { + $wordpress_time = $this->getTime($timestamp); + $steps[$internal_time] = $wordpress_time; + + $timestamp = strtotime($time_step, $timestamp); + $internal_time = $this->getTime($timestamp, self::INTERNAL_TIME_FORMAT); + } + + return $steps; + } +} diff --git a/views/newsletters.html b/views/newsletters.html index dc4c395ef8..ad2c89fe89 100644 --- a/views/newsletters.html +++ b/views/newsletters.html @@ -9,6 +9,9 @@ var mailpoet_settings = <%= json_encode(settings) %>; var mailpoet_lists = <%= json_encode(lists) %>; var mailpoet_roles = <%= json_encode(roles) %>; + var mailpoet_current_date = <%= json_encode(current_date) %>; + var mailpoet_current_time = <%= json_encode(current_time) %>; + var mailpoet_schedule_time_of_day = <%= json_encode(schedule_time_of_day) %>; <% endblock %> @@ -62,6 +65,7 @@ 'edit': __('Edit'), 'notSentYet': __('Not sent yet.'), 'scheduledFor': __('Scheduled for'), + 'scheduleIt': __('Schedule it'), 'newsletterQueueCompleted': __('Sent to %$1d of %$2d.'), 'resume': __('Resume'), 'pause': __('Pause'), @@ -145,5 +149,6 @@ 'saveDraftAndClose': __('Save as draft and close'), 'orSimply': __('or simply'), 'goBackToDesign': __('go back to design'), + 'localTimeIs': __('Local time is'), }) %> <% endblock %>