diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 85e21f4a12..3f062c9513 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -466,7 +466,7 @@ return array( 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', 'rsrc/js/core/behavior-search-typeahead.js' => '048330fa', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', - 'rsrc/js/core/behavior-time-typeahead.js' => '3416cef7', + 'rsrc/js/core/behavior-time-typeahead.js' => '8bfbb401', 'rsrc/js/core/behavior-toggle-class.js' => '5d7c9f33', 'rsrc/js/core/behavior-tokenizer.js' => 'b3a4b884', 'rsrc/js/core/behavior-tooltip.js' => '3ee3408b', @@ -641,7 +641,7 @@ return array( 'javelin-behavior-slowvote-embed' => '887ad43f', 'javelin-behavior-stripe-payment-form' => '3f5d6dbf', 'javelin-behavior-test-payment-form' => 'fc91ab6c', - 'javelin-behavior-time-typeahead' => '3416cef7', + 'javelin-behavior-time-typeahead' => '8bfbb401', 'javelin-behavior-toggle-class' => '5d7c9f33', 'javelin-behavior-typeahead-browse' => '635de1ec', 'javelin-behavior-typeahead-search' => '93d0c9e3', @@ -1041,14 +1041,6 @@ return array( '331b1611' => array( 'javelin-install', ), - '3416cef7' => array( - 'javelin-behavior', - 'javelin-util', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-vector', - 'javelin-typeahead-static-source', - ), '3975b470' => array( 'javelin-behavior', 'javelin-dom', @@ -1495,6 +1487,14 @@ return array( 'javelin-request', 'javelin-typeahead-source', ), + '8bfbb401' => array( + 'javelin-behavior', + 'javelin-util', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-vector', + 'javelin-typeahead-static-source', + ), '8ce821c5' => array( 'phabricator-notification', 'javelin-stratcom', diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index f77846dd21..41744edecf 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -21,6 +21,10 @@ final class PhabricatorCalendarEventEditController $error_end_date = true; $validation_exception = null; + $all_day_id = celerity_generate_unique_node_id(); + $start_date_id = celerity_generate_unique_node_id(); + $end_date_id = null; + if ($this->isCreate()) { $event = PhabricatorCalendarEvent::initializeNewCalendarEvent($user); list($start_value, $end_value) = $this->getDefaultTimeValues($user); @@ -31,6 +35,7 @@ final class PhabricatorCalendarEventEditController $subscribers = array(); $invitees = array($user_phid); $cancel_uri = $this->getApplicationURI(); + $end_date_id = celerity_generate_unique_node_id(); } else { $event = id(new PhabricatorCalendarEventQuery()) ->setViewer($user) @@ -178,10 +183,6 @@ final class PhabricatorCalendarEventEditController } } - $all_day_id = celerity_generate_unique_node_id(); - $start_date_id = celerity_generate_unique_node_id(); - $end_date_id = celerity_generate_unique_node_id(); - Javelin::initBehavior('event-all-day', array( 'allDayID' => $all_day_id, 'startDateID' => $start_date_id, @@ -209,7 +210,8 @@ final class PhabricatorCalendarEventEditController ->setError($error_start_date) ->setValue($start_value) ->setID($start_date_id) - ->setIsTimeDisabled($is_all_day); + ->setIsTimeDisabled($is_all_day) + ->setEndDateID($end_date_id); $end_control = id(new AphrontFormDateControl()) ->setUser($user) diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php index df0efe2c4a..2615c1c5ee 100644 --- a/src/view/form/control/AphrontFormDateControl.php +++ b/src/view/form/control/AphrontFormDateControl.php @@ -13,6 +13,7 @@ final class AphrontFormDateControl extends AphrontFormControl { private $continueOnInvalidDate = false; private $isTimeDisabled; private $isDisabled; + private $endDateID; public function setAllowNull($allow_null) { $this->allowNull = $allow_null; @@ -24,6 +25,11 @@ final class AphrontFormDateControl extends AphrontFormControl { return $this; } + public function setEndDateID($value) { + $this->endDateID = $value; + return $this; + } + const TIME_START_OF_DAY = 'start-of-day'; const TIME_END_OF_DAY = 'end-of-day'; const TIME_START_OF_BUSINESS = 'start-of-business'; @@ -274,7 +280,8 @@ final class AphrontFormDateControl extends AphrontFormControl { $time_id = celerity_generate_unique_node_id(); Javelin::initBehavior('time-typeahead', array( - 'timeID' => $time_id, + 'startTimeID' => $time_id, + 'endTimeID' => $this->endDateID, 'timeValues' => $values, )); diff --git a/webroot/rsrc/js/core/behavior-time-typeahead.js b/webroot/rsrc/js/core/behavior-time-typeahead.js index 158b8fbd4b..381eea7f36 100644 --- a/webroot/rsrc/js/core/behavior-time-typeahead.js +++ b/webroot/rsrc/js/core/behavior-time-typeahead.js @@ -9,7 +9,11 @@ */ JX.behavior('time-typeahead', function(config) { - var root = JX.$(config.timeID); + var start_date_control = JX.$(config.startTimeID); + var end_date_control = config.endTimeID ? JX.$(config.endTimeID) : null; + + var end_date_tampered = false; + var datasource = new JX.TypeaheadStaticSource(config.timeValues); datasource.setTransformer(function(v) { var attributes = {'className' : 'phui-time-typeahead-value'}; @@ -29,8 +33,91 @@ JX.behavior('time-typeahead', function(config) { }); datasource.setMaximumResultCount(24); var typeahead = new JX.Typeahead( - root, - JX.DOM.find(root, 'input', null)); + start_date_control, + JX.DOM.find(start_date_control, 'input', null)); typeahead.setDatasource(datasource); + + if (!end_date_control) { + typeahead.start(); + return; + } + + var start_time_control = JX.DOM.find( + start_date_control, + 'input', + 'time-input'); + var end_time_control = JX.DOM.find( + end_date_control, + 'input', + 'time-input'); + + JX.DOM.listen(start_time_control, 'input', null, function() { + if (end_date_tampered) { + return; + } + var time = start_time_control.value; + var end_value = getNewValue(time); + if (end_value) { + end_time_control.value = end_value; + } + }); + + typeahead.listen('choose', function(e) { + if (end_date_tampered) { + return; + } + var time = e.name; + var end_value = getNewValue(time); + if (end_value) { + end_time_control.value = end_value; + } + }); + + JX.DOM.listen(end_date_control, 'input', null, function() { + end_date_tampered = true; + }); + + + function getNewValue(time) { + var regex = /^([01]?\d)(?::([0-5]\d))?\s*((am|pm))?$/i; + + if (!regex.test(time)) { + return null; + } + + var results = regex.exec(time); + var hours = parseInt(results[1], 10); + var minutes = parseInt(results[2], 10) ? parseInt(results[2], 10) : 0; + + var real_time = 0; + + if (/pm/i.test(results[3])) { + real_time = 12*60; + } else if (/am/i.test(results[3]) && hours == 12) { + hours = 0; + } + + real_time = real_time + (hours * 60) + minutes; + + var end_time = real_time + 60; + + var end_meridian = 'AM'; + var end_hours = Math.floor(end_time / 60); + + if (end_hours == 12) { + end_meridian = 'PM'; + } else if (end_hours > 12 && end_hours < 24) { + end_hours = end_hours - 12; + end_meridian = 'PM'; + } else if (end_hours == 24) { + end_hours = end_hours - 12; + } + + var end_minutes = end_time%60; + end_minutes = (end_minutes > 9) ? end_minutes : ('0' + end_minutes); + var end_value = end_hours + ':' + end_minutes + ' ' + end_meridian; + return end_value; + } + typeahead.start(); });