@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.

Make bulk editor working set editable and more homogenous

Summary:
Ref T13025. See PHI50. Fixes T11286. Ref T10005. Begin modernizing the bulk editor.

For T10005 ("move the bulk editor to modern infrastructure"), rewrite the rendering of the editable set so that it is application-agnostic and can work with any kind of object.

For T11286 ("let users de-select items in the working set"), make the working set editable.

Test Plan:
{F5302158}

- Deselected some objects, applied an edit, saw the edit apply to only selected objects.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13025, T11286, T10005

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

+175 -29
+12 -5
resources/celerity/map.php
··· 9 9 'names' => array( 10 10 'conpherence.pkg.css' => 'e68cf1fa', 11 11 'conpherence.pkg.js' => '15191c65', 12 - 'core.pkg.css' => 'fdb27ef9', 12 + 'core.pkg.css' => '5be8063f', 13 13 'core.pkg.js' => '4c79d74f', 14 14 'darkconsole.pkg.js' => '1f9a31bc', 15 15 'differential.pkg.css' => '45951e9e', ··· 135 135 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', 136 136 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3', 137 137 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', 138 - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'bf094950', 138 + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '73c5f5c4', 139 139 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', 140 140 'rsrc/css/phui/phui-action-list.css' => 'f7f61a34', 141 141 'rsrc/css/phui/phui-action-panel.css' => 'b4798122', 142 142 'rsrc/css/phui/phui-badge.css' => '22c0cf4f', 143 143 'rsrc/css/phui/phui-basic-nav-view.css' => '98c11ab3', 144 144 'rsrc/css/phui/phui-big-info-view.css' => 'acc3492c', 145 - 'rsrc/css/phui/phui-box.css' => '9f3745fb', 145 + 'rsrc/css/phui/phui-box.css' => '4bd6cdb9', 146 146 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 147 147 'rsrc/css/phui/phui-cms.css' => '504b4b23', 148 148 'rsrc/css/phui/phui-comment-form.css' => 'ac68149f', ··· 523 523 'rsrc/js/core/phtize.js' => 'd254d646', 524 524 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => 'b95d6f7d', 525 525 'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb', 526 + 'rsrc/js/phui/behavior-phui-selectable-list.js' => '464259a2', 526 527 'rsrc/js/phui/behavior-phui-submenu.js' => 'a6f7a73b', 527 528 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 528 529 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', ··· 673 674 'javelin-behavior-phui-dropdown-menu' => 'b95d6f7d', 674 675 'javelin-behavior-phui-file-upload' => 'b003d4fb', 675 676 'javelin-behavior-phui-hovercards' => 'bcaccd64', 677 + 'javelin-behavior-phui-selectable-list' => '464259a2', 676 678 'javelin-behavior-phui-submenu' => 'a6f7a73b', 677 679 'javelin-behavior-phui-tab-group' => '0a0b10e9', 678 680 'javelin-behavior-phuix-example' => '68af71ca', ··· 820 822 'phui-badge-view-css' => '22c0cf4f', 821 823 'phui-basic-nav-view-css' => '98c11ab3', 822 824 'phui-big-info-view-css' => 'acc3492c', 823 - 'phui-box-css' => '9f3745fb', 825 + 'phui-box-css' => '4bd6cdb9', 824 826 'phui-button-bar-css' => 'f1ff5494', 825 827 'phui-button-css' => '1863cc6e', 826 828 'phui-button-simple-css' => '8e1baf68', ··· 860 862 'phui-oi-color-css' => 'cd2b9b77', 861 863 'phui-oi-drag-ui-css' => '08f4ccc3', 862 864 'phui-oi-flush-ui-css' => '9d9685d6', 863 - 'phui-oi-list-view-css' => 'bf094950', 865 + 'phui-oi-list-view-css' => '73c5f5c4', 864 866 'phui-oi-simple-ui-css' => 'a8beebea', 865 867 'phui-pager-css' => 'edcbc226', 866 868 'phui-pinboard-view-css' => '2495140e', ··· 1224 1226 ), 1225 1227 '453c5375' => array( 1226 1228 'javelin-behavior', 1229 + 'javelin-dom', 1230 + ), 1231 + '464259a2' => array( 1232 + 'javelin-behavior', 1233 + 'javelin-stratcom', 1227 1234 'javelin-dom', 1228 1235 ), 1229 1236 '469c0d9e' => array(
+53 -24
src/applications/maniphest/controller/ManiphestBatchEditController.php
··· 89 89 ->setURI($job->getMonitorURI()); 90 90 } 91 91 92 - $handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); 93 - 94 - $list = new ManiphestTaskListView(); 95 - $list->setTasks($tasks); 96 - $list->setUser($viewer); 97 - $list->setHandles($handles); 92 + $list = $this->newBulkObjectList($tasks); 98 93 99 94 $template = new AphrontTokenizerTemplateView(); 100 95 $template = $template->render(); ··· 142 137 'statusMap' => ManiphestTaskStatus::getTaskStatusMap(), 143 138 )); 144 139 145 - $form = id(new AphrontFormView()) 146 - ->setUser($viewer) 147 - ->addHiddenInput('board', $board_id) 148 - ->setID('maniphest-batch-edit-form'); 149 - 150 - foreach ($tasks as $task) { 151 - $form->appendChild( 152 - phutil_tag( 153 - 'input', 154 - array( 155 - 'type' => 'hidden', 156 - 'name' => 'batch[]', 157 - 'value' => $task->getID(), 158 - ))); 159 - } 140 + $form = id(new PHUIFormLayoutView()) 141 + ->setUser($viewer); 160 142 161 143 $form->appendChild( 162 144 phutil_tag( ··· 166 148 'name' => 'actions', 167 149 'id' => 'batch-form-actions', 168 150 ))); 151 + 169 152 $form->appendChild( 170 153 id(new PHUIFormInsetView()) 171 154 ->setTitle(pht('Actions')) ··· 210 193 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 211 194 ->setForm($form); 212 195 213 - $view = id(new PHUITwoColumnView()) 214 - ->setHeader($header) 215 - ->setFooter(array( 196 + 197 + $complete_form = phabricator_form( 198 + $viewer, 199 + array( 200 + 'action' => $request->getRequestURI(), 201 + 'method' => 'POST', 202 + 'id' => 'maniphest-batch-edit-form', 203 + ), 204 + array( 205 + phutil_tag( 206 + 'input', 207 + array( 208 + 'type' => 'hidden', 209 + 'name' => 'board', 210 + 'value' => $board_id, 211 + )), 216 212 $task_box, 217 213 $form_box, 218 214 )); 219 215 216 + $view = id(new PHUITwoColumnView()) 217 + ->setHeader($header) 218 + ->setFooter($complete_form); 219 + 220 220 return $this->newPage() 221 221 ->setTitle($title) 222 222 ->setCrumbs($crumbs) 223 223 ->appendChild($view); 224 + } 225 + 226 + private function newBulkObjectList(array $objects) { 227 + $viewer = $this->getViewer(); 228 + $objects = mpull($objects, null, 'getPHID'); 229 + 230 + $handles = $viewer->loadHandles(array_keys($objects)); 231 + 232 + $status_closed = PhabricatorObjectHandle::STATUS_CLOSED; 233 + 234 + $list = id(new PHUIObjectItemListView()) 235 + ->setViewer($viewer) 236 + ->setFlush(true); 237 + 238 + foreach ($objects as $phid => $object) { 239 + $handle = $handles[$phid]; 240 + 241 + $is_closed = ($handle->getStatus() === $status_closed); 242 + 243 + $item = id(new PHUIObjectItemView()) 244 + ->setHeader($handle->getFullName()) 245 + ->setHref($handle->getURI()) 246 + ->setDisabled($is_closed) 247 + ->setSelectable('batch[]', $object->getID(), true); 248 + 249 + $list->addItem($item); 250 + } 251 + 252 + return $list; 224 253 } 225 254 226 255 }
+43
src/view/phui/PHUIObjectItemView.php
··· 29 29 private $coverImage; 30 30 private $description; 31 31 32 + private $selectableName; 33 + private $selectableValue; 34 + private $isSelected; 35 + 32 36 public function setDisabled($disabled) { 33 37 $this->disabled = $disabled; 34 38 return $this; ··· 160 164 return $this; 161 165 } 162 166 167 + public function setSelectable($name, $value, $is_selected) { 168 + $this->selectableName = $name; 169 + $this->selectableValue = $value; 170 + $this->isSelected = $is_selected; 171 + return $this; 172 + } 173 + 163 174 public function setEpoch($epoch) { 164 175 $date = phabricator_datetime($epoch, $this->getUser()); 165 176 $this->addIcon('none', $date); ··· 239 250 } 240 251 241 252 protected function getTagAttributes() { 253 + $sigils = array(); 254 + 242 255 $item_classes = array(); 243 256 $item_classes[] = 'phui-oi'; 244 257 ··· 286 299 throw new Exception(pht('Invalid effect!')); 287 300 } 288 301 302 + if ($this->isSelected) { 303 + $item_classes[] = 'phui-oi-selected'; 304 + } 305 + 306 + if ($this->selectableName !== null) { 307 + $item_classes[] = 'phui-oi-selectable'; 308 + $sigils[] = 'phui-oi-selectable'; 309 + 310 + Javelin::initBehavior('phui-selectable-list'); 311 + } 312 + 289 313 if ($this->getGrippable()) { 290 314 $item_classes[] = 'phui-oi-grippable'; 291 315 } ··· 300 324 301 325 return array( 302 326 'class' => $item_classes, 327 + 'sigil' => $sigils, 303 328 ); 304 329 } 305 330 ··· 626 651 'class' => 'phui-oi-col0 phui-oi-countdown', 627 652 ), 628 653 $countdown); 654 + } 655 + 656 + if ($this->selectableName !== null) { 657 + $checkbox = phutil_tag( 658 + 'input', 659 + array( 660 + 'type' => 'checkbox', 661 + 'name' => $this->selectableName, 662 + 'value' => $this->selectableValue, 663 + 'checked' => ($this->isSelected ? 'checked' : null), 664 + )); 665 + 666 + $column0 = phutil_tag( 667 + 'div', 668 + array( 669 + 'class' => 'phui-oi-col0 phui-oi-checkbox', 670 + ), 671 + $checkbox); 629 672 } 630 673 631 674 $column1 = phutil_tag(
+19
webroot/rsrc/css/phui/object-item/phui-oi-list-view.css
··· 664 664 padding: 0 8px 8px; 665 665 text-align: left; 666 666 } 667 + 668 + .phui-oi-col0.phui-oi-checkbox { 669 + width: 28px; 670 + text-align: center; 671 + } 672 + 673 + .phui-oi-selectable { 674 + cursor: pointer; 675 + user-select: none; 676 + -webkit-user-select: none; 677 + } 678 + 679 + /* When the list selection state can be toggled on the client (as in the bulk 680 + editor), keep the border color consistent to make the interaction feel more 681 + robust. */ 682 + ul.phui-oi-list-view .phui-oi-selectable 683 + .phui-oi-frame { 684 + border-color: {$blueborder}; 685 + }
+4
webroot/rsrc/css/phui/phui-box.css
··· 103 103 padding: 2px 8px; 104 104 } 105 105 106 + .phui-box-blue-property .phui-oi-list-view.phui-oi-list-flush { 107 + padding: 0; 108 + } 109 + 106 110 body .phui-box-blue-property.phui-object-box.phui-object-box-collapsed { 107 111 padding: 0; 108 112 }
+44
webroot/rsrc/js/phui/behavior-phui-selectable-list.js
··· 1 + /** 2 + * @provides javelin-behavior-phui-selectable-list 3 + * @requires javelin-behavior 4 + * javelin-stratcom 5 + * javelin-dom 6 + */ 7 + 8 + JX.behavior('phui-selectable-list', function() { 9 + 10 + JX.Stratcom.listen('click', 'phui-oi-selectable', function(e) { 11 + if (!e.isNormalClick()) { 12 + return; 13 + } 14 + 15 + // If the user clicked a link, ignore it. 16 + if (e.getNode('tag:a')) { 17 + return; 18 + } 19 + 20 + var root = e.getNode('phui-oi-selectable'); 21 + 22 + // If the user did not click the checkbox, pretend they did. This makes 23 + // the entire element a click target to make changing the selection set a 24 + // bit easier. 25 + if (!e.getNode('tag:input')) { 26 + var checkbox = getCheckbox(root); 27 + checkbox.checked = !checkbox.checked; 28 + 29 + e.kill(); 30 + } 31 + 32 + setTimeout(JX.bind(null, redraw, root), 0); 33 + }); 34 + 35 + function getCheckbox(root) { 36 + return JX.DOM.find(root, 'input'); 37 + } 38 + 39 + function redraw(root) { 40 + var checkbox = getCheckbox(root); 41 + JX.DOM.alterClass(root, 'phui-oi-selected', !!checkbox.checked); 42 + } 43 + 44 + });