@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Allow datetime inputs to be optional

Summary:
Fixes T3279. For ApplicationSearch (and in some other cases) I'd like users to be able to provide an optional date. This isn't currently possible.

Add a checkbox which disables or enables the input.

Test Plan: Used UIExample to enter dates. Used Calendar to enter dates.

Reviewers: chad, btrahan

Reviewed By: chad

CC: aran

Maniphest Tasks: T3279

Differential Revision: https://secure.phabricator.com/D6082

+203 -87
+48 -48
src/__celerity_resource_map__.php
··· 800 800 ), 801 801 'aphront-form-view-css' => 802 802 array( 803 - 'uri' => '/res/656ca1a3/rsrc/css/aphront/form-view.css', 803 + 'uri' => '/res/4fe4c174/rsrc/css/aphront/form-view.css', 804 804 'type' => 'css', 805 805 'requires' => 806 806 array( ··· 1641 1641 ), 1642 1642 'javelin-behavior-fancy-datepicker' => 1643 1643 array( 1644 - 'uri' => '/res/f5c13e07/rsrc/js/core/behavior-fancy-datepicker.js', 1644 + 'uri' => '/res/dcd7c2ca/rsrc/js/core/behavior-fancy-datepicker.js', 1645 1645 'type' => 'js', 1646 1646 'requires' => 1647 1647 array( ··· 3657 3657 ), 3658 3658 'phui-form-css' => 3659 3659 array( 3660 - 'uri' => '/res/ba4d7995/rsrc/css/phui/phui-form.css', 3660 + 'uri' => '/res/eb478e83/rsrc/css/phui/phui-form.css', 3661 3661 'type' => 'css', 3662 3662 'requires' => 3663 3663 array( ··· 3973 3973 ), array( 3974 3974 'packages' => 3975 3975 array( 3976 - 'b7428f7c' => 3976 + '1b14560c' => 3977 3977 array( 3978 3978 'name' => 'core.pkg.css', 3979 3979 'symbols' => ··· 4022 4022 41 => 'phabricator-property-list-view-css', 4023 4023 42 => 'phabricator-tag-view-css', 4024 4024 ), 4025 - 'uri' => '/res/pkg/b7428f7c/core.pkg.css', 4025 + 'uri' => '/res/pkg/1b14560c/core.pkg.css', 4026 4026 'type' => 'css', 4027 4027 ), 4028 4028 '98f60e3f' => ··· 4216 4216 'reverse' => 4217 4217 array( 4218 4218 'aphront-attached-file-view-css' => '6b1fccc6', 4219 - 'aphront-dialog-view-css' => 'b7428f7c', 4220 - 'aphront-error-view-css' => 'b7428f7c', 4221 - 'aphront-form-view-css' => 'b7428f7c', 4222 - 'aphront-list-filter-view-css' => 'b7428f7c', 4223 - 'aphront-pager-view-css' => 'b7428f7c', 4224 - 'aphront-panel-view-css' => 'b7428f7c', 4225 - 'aphront-table-view-css' => 'b7428f7c', 4226 - 'aphront-tokenizer-control-css' => 'b7428f7c', 4227 - 'aphront-tooltip-css' => 'b7428f7c', 4228 - 'aphront-typeahead-control-css' => 'b7428f7c', 4219 + 'aphront-dialog-view-css' => '1b14560c', 4220 + 'aphront-error-view-css' => '1b14560c', 4221 + 'aphront-form-view-css' => '1b14560c', 4222 + 'aphront-list-filter-view-css' => '1b14560c', 4223 + 'aphront-pager-view-css' => '1b14560c', 4224 + 'aphront-panel-view-css' => '1b14560c', 4225 + 'aphront-table-view-css' => '1b14560c', 4226 + 'aphront-tokenizer-control-css' => '1b14560c', 4227 + 'aphront-tooltip-css' => '1b14560c', 4228 + 'aphront-typeahead-control-css' => '1b14560c', 4229 4229 'differential-changeset-view-css' => 'dd27a69b', 4230 4230 'differential-core-view-css' => 'dd27a69b', 4231 4231 'differential-inline-comment-editor' => '9488bb69', ··· 4239 4239 'differential-table-of-contents-css' => 'dd27a69b', 4240 4240 'diffusion-commit-view-css' => 'c8ce2d88', 4241 4241 'diffusion-icons-css' => 'c8ce2d88', 4242 - 'global-drag-and-drop-css' => 'b7428f7c', 4242 + 'global-drag-and-drop-css' => '1b14560c', 4243 4243 'inline-comment-summary-css' => 'dd27a69b', 4244 4244 'javelin-aphlict' => '98f60e3f', 4245 4245 'javelin-behavior' => 'c1359b5d', ··· 4313 4313 'javelin-util' => 'c1359b5d', 4314 4314 'javelin-vector' => 'c1359b5d', 4315 4315 'javelin-workflow' => 'c1359b5d', 4316 - 'lightbox-attachment-css' => 'b7428f7c', 4316 + 'lightbox-attachment-css' => '1b14560c', 4317 4317 'maniphest-task-summary-css' => '6b1fccc6', 4318 4318 'maniphest-transaction-detail-css' => '6b1fccc6', 4319 - 'phabricator-action-list-view-css' => 'b7428f7c', 4320 - 'phabricator-application-launch-view-css' => 'b7428f7c', 4319 + 'phabricator-action-list-view-css' => '1b14560c', 4320 + 'phabricator-application-launch-view-css' => '1b14560c', 4321 4321 'phabricator-busy' => '98f60e3f', 4322 4322 'phabricator-content-source-view-css' => 'dd27a69b', 4323 - 'phabricator-core-buttons-css' => 'b7428f7c', 4324 - 'phabricator-core-css' => 'b7428f7c', 4325 - 'phabricator-crumbs-view-css' => 'b7428f7c', 4326 - 'phabricator-directory-css' => 'b7428f7c', 4323 + 'phabricator-core-buttons-css' => '1b14560c', 4324 + 'phabricator-core-css' => '1b14560c', 4325 + 'phabricator-crumbs-view-css' => '1b14560c', 4326 + 'phabricator-directory-css' => '1b14560c', 4327 4327 'phabricator-drag-and-drop-file-upload' => '9488bb69', 4328 4328 'phabricator-dropdown-menu' => '98f60e3f', 4329 4329 'phabricator-file-upload' => '98f60e3f', 4330 - 'phabricator-filetree-view-css' => 'b7428f7c', 4331 - 'phabricator-flag-css' => 'b7428f7c', 4332 - 'phabricator-form-view-css' => 'b7428f7c', 4333 - 'phabricator-header-view-css' => 'b7428f7c', 4330 + 'phabricator-filetree-view-css' => '1b14560c', 4331 + 'phabricator-flag-css' => '1b14560c', 4332 + 'phabricator-form-view-css' => '1b14560c', 4333 + 'phabricator-header-view-css' => '1b14560c', 4334 4334 'phabricator-hovercard' => '98f60e3f', 4335 - 'phabricator-jump-nav' => 'b7428f7c', 4335 + 'phabricator-jump-nav' => '1b14560c', 4336 4336 'phabricator-keyboard-shortcut' => '98f60e3f', 4337 4337 'phabricator-keyboard-shortcut-manager' => '98f60e3f', 4338 - 'phabricator-main-menu-view' => 'b7428f7c', 4338 + 'phabricator-main-menu-view' => '1b14560c', 4339 4339 'phabricator-menu-item' => '98f60e3f', 4340 - 'phabricator-nav-view-css' => 'b7428f7c', 4340 + 'phabricator-nav-view-css' => '1b14560c', 4341 4341 'phabricator-notification' => '98f60e3f', 4342 - 'phabricator-notification-css' => 'b7428f7c', 4343 - 'phabricator-notification-menu-css' => 'b7428f7c', 4344 - 'phabricator-object-item-list-view-css' => 'b7428f7c', 4342 + 'phabricator-notification-css' => '1b14560c', 4343 + 'phabricator-notification-menu-css' => '1b14560c', 4344 + 'phabricator-object-item-list-view-css' => '1b14560c', 4345 4345 'phabricator-object-selector-css' => 'dd27a69b', 4346 4346 'phabricator-phtize' => '98f60e3f', 4347 4347 'phabricator-prefab' => '98f60e3f', 4348 4348 'phabricator-project-tag-css' => '6b1fccc6', 4349 - 'phabricator-property-list-view-css' => 'b7428f7c', 4350 - 'phabricator-remarkup-css' => 'b7428f7c', 4349 + 'phabricator-property-list-view-css' => '1b14560c', 4350 + 'phabricator-remarkup-css' => '1b14560c', 4351 4351 'phabricator-shaped-request' => '9488bb69', 4352 - 'phabricator-side-menu-view-css' => 'b7428f7c', 4353 - 'phabricator-standard-page-view' => 'b7428f7c', 4354 - 'phabricator-tag-view-css' => 'b7428f7c', 4352 + 'phabricator-side-menu-view-css' => '1b14560c', 4353 + 'phabricator-standard-page-view' => '1b14560c', 4354 + 'phabricator-tag-view-css' => '1b14560c', 4355 4355 'phabricator-textareautils' => '98f60e3f', 4356 4356 'phabricator-tooltip' => '98f60e3f', 4357 - 'phabricator-transaction-view-css' => 'b7428f7c', 4358 - 'phabricator-zindex-css' => 'b7428f7c', 4359 - 'phui-form-css' => 'b7428f7c', 4360 - 'phui-icon-view-css' => 'b7428f7c', 4361 - 'spacing-css' => 'b7428f7c', 4362 - 'sprite-apps-large-css' => 'b7428f7c', 4363 - 'sprite-gradient-css' => 'b7428f7c', 4364 - 'sprite-icons-css' => 'b7428f7c', 4365 - 'sprite-menu-css' => 'b7428f7c', 4366 - 'syntax-highlighting-css' => 'b7428f7c', 4357 + 'phabricator-transaction-view-css' => '1b14560c', 4358 + 'phabricator-zindex-css' => '1b14560c', 4359 + 'phui-form-css' => '1b14560c', 4360 + 'phui-icon-view-css' => '1b14560c', 4361 + 'spacing-css' => '1b14560c', 4362 + 'sprite-apps-large-css' => '1b14560c', 4363 + 'sprite-gradient-css' => '1b14560c', 4364 + 'sprite-icons-css' => '1b14560c', 4365 + 'sprite-menu-css' => '1b14560c', 4366 + 'syntax-highlighting-css' => '1b14560c', 4367 4367 ), 4368 4368 ));
+13 -2
src/applications/uiexample/examples/PhabricatorFormExample.php
··· 19 19 ->setName('start') 20 20 ->setLabel('Start') 21 21 ->setInitialTime(AphrontFormDateControl::TIME_START_OF_BUSINESS); 22 - $start_value = $start_time->readValueFromRequest($request); 23 22 24 23 $end_time = id(new AphrontFormDateControl()) 25 24 ->setUser($user) 26 25 ->setName('end') 27 26 ->setLabel('End') 28 27 ->setInitialTime(AphrontFormDateControl::TIME_END_OF_BUSINESS); 29 - $end_value = $end_time->readValueFromRequest($request); 28 + 29 + $null_time = id(new AphrontFormDateControl()) 30 + ->setUser($user) 31 + ->setName('nulltime') 32 + ->setLabel('Nullable') 33 + ->setAllowNull(true); 34 + 35 + if ($request->isFormPost()) { 36 + $start_value = $start_time->readValueFromRequest($request); 37 + $end_value = $end_time->readValueFromRequest($request); 38 + $null_value = $null_time->readValueFromRequest($request); 39 + } 30 40 31 41 $form = id(new AphrontFormView()) 32 42 ->setUser($user) 33 43 ->setFlexible(true) 34 44 ->appendChild($start_time) 35 45 ->appendChild($end_time) 46 + ->appendChild($null_time) 36 47 ->appendChild( 37 48 id(new AphrontFormSubmitControl()) 38 49 ->setValue('Submit'));
+100 -36
src/view/form/control/AphrontFormDateControl.php
··· 3 3 final class AphrontFormDateControl extends AphrontFormControl { 4 4 5 5 private $initialTime; 6 + private $zone; 6 7 7 8 private $valueDay; 8 9 private $valueMonth; 9 10 private $valueYear; 10 11 private $valueTime; 12 + private $allowNull; 13 + 14 + public function setAllowNull($allow_null) { 15 + $this->allowNull = $allow_null; 16 + return $this; 17 + } 11 18 12 19 const TIME_START_OF_DAY = 'start-of-day'; 13 20 const TIME_END_OF_DAY = 'end-of-day'; ··· 20 27 } 21 28 22 29 public function readValueFromRequest(AphrontRequest $request) { 23 - $user = $this->user; 24 - if (!$this->user) { 25 - throw new Exception( 26 - pht("Call setUser() before readValueFromRequest()!")); 27 - } 28 - 29 - $user_zone = $user->getTimezoneIdentifier(); 30 - $zone = new DateTimeZone($user_zone); 31 - 32 30 $day = $request->getInt($this->getDayInputName()); 33 31 $month = $request->getInt($this->getMonthInputName()); 34 32 $year = $request->getInt($this->getYearInputName()); 35 33 $time = $request->getStr($this->getTimeInputName()); 34 + $enabled = $request->getBool($this->getCheckboxInputName()); 35 + 36 + if ($this->allowNull && !$enabled) { 37 + $this->setError(null); 38 + $this->setValue(null); 39 + return; 40 + } 36 41 37 42 $err = $this->getError(); 38 43 ··· 44 49 45 50 // Assume invalid. 46 51 $err = 'Invalid'; 52 + 53 + $zone = $this->getTimezone(); 47 54 48 55 try { 49 56 $date = new DateTime("{$year}-{$month}-{$day} {$time}", $zone); ··· 59 66 $this->setValue(null); 60 67 } 61 68 } else { 62 - // TODO: We could eventually allow these to be customized per install or 63 - // per user or both, but let's wait and see. 64 - switch ($this->initialTime) { 65 - case self::TIME_START_OF_DAY: 66 - default: 67 - $time = '12:00 AM'; 68 - break; 69 - case self::TIME_START_OF_BUSINESS: 70 - $time = '9:00 AM'; 71 - break; 72 - case self::TIME_END_OF_BUSINESS: 73 - $time = '5:00 PM'; 74 - break; 75 - case self::TIME_END_OF_DAY: 76 - $time = '11:59 PM'; 77 - break; 78 - } 79 - 80 - $today = $this->formatTime(time(), 'Y-m-d'); 81 - try { 82 - $date = new DateTime("{$today} {$time}", $zone); 83 - $value = $date->format('U'); 84 - } catch (Exception $ex) { 85 - $value = null; 86 - } 87 - 69 + $value = $this->getInitialValue(); 88 70 if ($value) { 89 71 $this->setValue($value); 90 72 } else { ··· 176 158 return $this->getName().'_t'; 177 159 } 178 160 161 + private function getCheckboxInputName() { 162 + return $this->getName().'_e'; 163 + } 164 + 179 165 protected function renderInput() { 166 + 167 + $disabled = null; 168 + if ($this->getValue() === null) { 169 + $this->setValue($this->getInitialValue()); 170 + if ($this->allowNull) { 171 + $disabled = 'disabled'; 172 + } 173 + } 174 + 180 175 $min_year = $this->getMinYear(); 181 176 $max_year = $this->getMaxYear(); 182 177 ··· 198 193 12 => pht('Dec'), 199 194 ); 200 195 196 + $checkbox = null; 197 + if ($this->allowNull) { 198 + $checkbox = javelin_tag( 199 + 'input', 200 + array( 201 + 'type' => 'checkbox', 202 + 'name' => $this->getCheckboxInputName(), 203 + 'sigil' => 'calendar-enable', 204 + 'class' => 'aphront-form-date-enabled-input', 205 + 'value' => 1, 206 + 'checked' => ($disabled === null ? 'checked' : null), 207 + )); 208 + } 209 + 201 210 $years = range($this->getMinYear(), $this->getMaxYear()); 202 211 $years = array_fuse($years); 203 212 ··· 207 216 array( 208 217 'name' => $this->getDayInputName(), 209 218 'sigil' => 'day-input', 219 + 'disabled' => $disabled, 210 220 )); 211 221 212 222 $months_sel = AphrontFormSelectControl::renderSelectTag( ··· 215 225 array( 216 226 'name' => $this->getMonthInputName(), 217 227 'sigil' => 'month-input', 228 + 'disabled' => $disabled, 218 229 )); 219 230 220 231 $years_sel = AphrontFormSelectControl::renderSelectTag( ··· 223 234 array( 224 235 'name' => $this->getYearInputName(), 225 236 'sigil' => 'year-input', 237 + 'disabled' => $disabled, 226 238 )); 227 239 228 240 $cal_icon = javelin_tag( ··· 234 246 ), 235 247 ''); 236 248 237 - $time_sel = phutil_tag( 249 + $time_sel = javelin_tag( 238 250 'input', 239 251 array( 240 252 'name' => $this->getTimeInputName(), ··· 242 254 'value' => $this->getTimeInputValue(), 243 255 'type' => 'text', 244 256 'class' => 'aphront-form-date-time-input', 257 + 'disabled' => $disabled, 245 258 ), 246 259 ''); 247 260 ··· 252 265 array( 253 266 'class' => 'aphront-form-date-container', 254 267 'sigil' => 'phabricator-date-control', 268 + 'meta' => array( 269 + 'disabled' => (bool)$disabled, 270 + ), 255 271 ), 256 272 array( 273 + $checkbox, 257 274 $days_sel, 258 275 $months_sel, 259 276 $years_sel, 260 277 $cal_icon, 261 278 $time_sel, 262 279 )); 280 + } 281 + 282 + private function getTimezone() { 283 + if ($this->zone) { 284 + return $this->zone; 285 + } 286 + 287 + $user = $this->getUser(); 288 + if (!$this->getUser()) { 289 + throw new Exception("Call setUser() before getTimezone()!"); 290 + } 291 + 292 + $user_zone = $user->getTimezoneIdentifier(); 293 + $this->zone = new DateTimeZone($user_zone); 294 + return $this->zone; 295 + } 296 + 297 + private function getInitialValue() { 298 + $zone = $this->getTimezone(); 299 + 300 + // TODO: We could eventually allow these to be customized per install or 301 + // per user or both, but let's wait and see. 302 + switch ($this->initialTime) { 303 + case self::TIME_START_OF_DAY: 304 + default: 305 + $time = '12:00 AM'; 306 + break; 307 + case self::TIME_START_OF_BUSINESS: 308 + $time = '9:00 AM'; 309 + break; 310 + case self::TIME_END_OF_BUSINESS: 311 + $time = '5:00 PM'; 312 + break; 313 + case self::TIME_END_OF_DAY: 314 + $time = '11:59 PM'; 315 + break; 316 + } 317 + 318 + $today = $this->formatTime(time(), 'Y-m-d'); 319 + try { 320 + $date = new DateTime("{$today} {$time}", $zone); 321 + $value = $date->format('U'); 322 + } catch (Exception $ex) { 323 + $value = null; 324 + } 325 + 326 + return $value; 263 327 } 264 328 265 329 }
+6
webroot/rsrc/css/aphront/form-view.css
··· 278 278 margin: 2px; 279 279 display: inline; 280 280 } 281 + .aphront-form-date-container input.aphront-form-date-enabled-input { 282 + width: auto; 283 + display: inline; 284 + margin-right: 8px; 285 + font-size: 16px; 286 + } 281 287 282 288 .aphront-form-date-container input.aphront-form-date-time-input { 283 289 width: 7em;
+5
webroot/rsrc/css/phui/phui-form.css
··· 127 127 textarea::-webkit-input-placeholder { 128 128 color: #999999; 129 129 } 130 + 131 + 132 + select[disabled="disabled"] { 133 + opacity: 0.5; 134 + }
+31 -1
webroot/rsrc/js/core/behavior-fancy-datepicker.js
··· 70 70 root = null; 71 71 }; 72 72 73 + var ontoggle = function(e) { 74 + var box = e.getTarget(); 75 + root = e.getNode('phabricator-date-control'); 76 + JX.Stratcom.getData(root).disabled = !box.checked; 77 + redraw_inputs(); 78 + }; 79 + 73 80 var get_inputs = function() { 74 81 return { 75 82 y: JX.DOM.find(root, 'select', 'year-input'), 76 83 m: JX.DOM.find(root, 'select', 'month-input'), 77 - d: JX.DOM.find(root, 'select', 'day-input') 84 + d: JX.DOM.find(root, 'select', 'day-input'), 85 + t: JX.DOM.find(root, 'input', 'time-input') 78 86 }; 79 87 }; 80 88 ··· 99 107 render_month(), 100 108 render_day() 101 109 ]); 110 + }; 111 + 112 + var redraw_inputs = function() { 113 + var inputs = get_inputs(); 114 + var disabled = JX.Stratcom.getData(root).disabled; 115 + for (var k in inputs) { 116 + if (disabled) { 117 + inputs[k].setAttribute('disabled', 'disabled'); 118 + } else { 119 + inputs[k].removeAttribute('disabled'); 120 + } 121 + } 122 + 123 + var box = JX.DOM.scry(root, 'input', 'calendar-enable'); 124 + if (box.length) { 125 + box[0].checked = !disabled; 126 + } 102 127 }; 103 128 104 129 // Render a cell for the date picker. ··· 201 226 202 227 203 228 JX.Stratcom.listen('click', 'calendar-button', onopen); 229 + JX.Stratcom.listen('change', 'calendar-enable', ontoggle); 204 230 205 231 JX.Stratcom.listen( 206 232 'click', ··· 236 262 setTimeout(JX.bind(null, onclose, e), 150); 237 263 break; 238 264 } 265 + 266 + // Enable the control. 267 + JX.Stratcom.getData(root).disabled = false; 268 + redraw_inputs(); 239 269 240 270 render(); 241 271 });