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

When using the "Close as Duplicate" relationship action, limit the UI to 1 task

Summary:
Ref T4788. When closing a task as a duplicate of another task, you can only select one task, since it doesn't really make sense to merge one task into several other tasks (this operation is //possible//, but probably not what anyone ever wants to do, I think?).

Make the UI understand this: after you select a task, disable all of the "select" buttons in the UI to make this clear.

Test Plan:
- Used "Close as Duplicate", only allowed to select 1 task.
- Used other editors like "Merge Duplicates In", allowed to select lots of tasks.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T4788

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

+129 -52
+9 -9
resources/celerity/map.php
··· 11 11 'core.pkg.js' => 'f2139810', 12 12 'darkconsole.pkg.js' => 'e7393ebb', 13 13 'differential.pkg.css' => '3e81ae60', 14 - 'differential.pkg.js' => '01a010d6', 14 + 'differential.pkg.js' => '634399e9', 15 15 'diffusion.pkg.css' => '91c5d3a6', 16 16 'diffusion.pkg.js' => '3a9a8bfa', 17 17 'maniphest.pkg.css' => '4845691a', ··· 495 495 'rsrc/js/core/behavior-lightbox-attachments.js' => 'f8ba29d7', 496 496 'rsrc/js/core/behavior-line-linker.js' => '1499a8cb', 497 497 'rsrc/js/core/behavior-more.js' => 'a80d0378', 498 - 'rsrc/js/core/behavior-object-selector.js' => '9030ebef', 498 + 'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f', 499 499 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 500 500 'rsrc/js/core/behavior-phabricator-nav.js' => '56a1ca03', 501 501 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '116cf19b', ··· 658 658 'javelin-behavior-phabricator-line-linker' => '1499a8cb', 659 659 'javelin-behavior-phabricator-nav' => '56a1ca03', 660 660 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 661 - 'javelin-behavior-phabricator-object-selector' => '9030ebef', 661 + 'javelin-behavior-phabricator-object-selector' => 'e0ec7f2f', 662 662 'javelin-behavior-phabricator-oncopy' => '2926fff2', 663 663 'javelin-behavior-phabricator-remarkup-assist' => '116cf19b', 664 664 'javelin-behavior-phabricator-reveal-content' => '60821bc7', ··· 1608 1608 'javelin-dom', 1609 1609 'javelin-request', 1610 1610 ), 1611 - '9030ebef' => array( 1612 - 'javelin-behavior', 1613 - 'javelin-dom', 1614 - 'javelin-request', 1615 - 'javelin-util', 1616 - ), 1617 1611 '9196fb06' => array( 1618 1612 'javelin-install', 1619 1613 'javelin-dom', ··· 2035 2029 ), 2036 2030 'df5e11d2' => array( 2037 2031 'javelin-install', 2032 + ), 2033 + 'e0ec7f2f' => array( 2034 + 'javelin-behavior', 2035 + 'javelin-dom', 2036 + 'javelin-request', 2037 + 'javelin-util', 2038 2038 ), 2039 2039 'e10f8e18' => array( 2040 2040 'javelin-behavior',
+4 -9
src/applications/maniphest/relationship/ManiphestTaskCloseAsDuplicateRelationship.php
··· 53 53 return false; 54 54 } 55 55 56 + public function getMaximumSelectionSize() { 57 + return 1; 58 + } 59 + 56 60 public function willUpdateRelationships($object, array $add, array $rem) { 57 - 58 - // TODO: Communicate this in the UI before users hit this error. 59 - if (count($add) > 1) { 60 - throw new Exception( 61 - pht( 62 - 'A task can only be closed as a duplicate of exactly one other '. 63 - 'task.')); 64 - } 65 - 66 61 $task = head($add); 67 62 return $this->newMergeIntoTransactions($task); 68 63 }
+13
src/applications/search/controller/PhabricatorSearchRelationshipController.php
··· 39 39 $done_uri = $src_handle->getURI(); 40 40 $initial_phids = $dst_phids; 41 41 42 + $maximum = $relationship->getMaximumSelectionSize(); 43 + 42 44 if ($request->isFormPost()) { 43 45 $phids = explode(';', $request->getStr('phids')); 44 46 $phids = array_filter($phids); 45 47 $phids = array_values($phids); 48 + 49 + // The UI normally enforces this with Javascript, so this is just a 50 + // sanity check and does not need to be particularly user-friendly. 51 + if ($maximum && (count($phids) > $maximum)) { 52 + throw new Exception( 53 + pht( 54 + 'Too many relationships (%s, of type "%s").', 55 + phutil_count($phids), 56 + $relationship->getRelationshipConstant())); 57 + } 46 58 47 59 $initial_phids = $request->getStrList('initialPHIDs'); 48 60 ··· 175 187 ->setHeader($dialog_header) 176 188 ->setButtonText($dialog_button) 177 189 ->setInstructions($dialog_instructions) 190 + ->setMaximumSelectionSize($maximum) 178 191 ->buildDialog(); 179 192 } 180 193
+4
src/applications/search/relationship/PhabricatorObjectRelationship.php
··· 104 104 return "/search/rel/{$type}/{$phid}/"; 105 105 } 106 106 107 + public function getMaximumSelectionSize() { 108 + return null; 109 + } 110 + 107 111 public function canUndoRelationship() { 108 112 return true; 109 113 }
+13
src/view/control/PhabricatorObjectSelectorDialog.php
··· 11 11 private $selectedFilter; 12 12 private $excluded; 13 13 private $initialPHIDs; 14 + private $maximumSelectionSize; 14 15 15 16 private $title; 16 17 private $header; ··· 85 86 86 87 public function getInitialPHIDs() { 87 88 return $this->initialPHIDs; 89 + } 90 + 91 + public function setMaximumSelectionSize($maximum_selection_size) { 92 + $this->maximumSelectionSize = $maximum_selection_size; 93 + return $this; 94 + } 95 + 96 + public function getMaximumSelectionSize() { 97 + return $this->maximumSelectionSize; 88 98 } 89 99 90 100 public function buildDialog() { ··· 190 200 $dialog->addHiddenInput('initialPHIDs', $initial_phids); 191 201 } 192 202 203 + $maximum = $this->getMaximumSelectionSize(); 204 + 193 205 Javelin::initBehavior( 194 206 'phabricator-object-selector', 195 207 array( ··· 202 214 'exclude' => $this->excluded, 203 215 'uri' => $this->searchURI, 204 216 'handles' => $handle_views, 217 + 'maximum' => $maximum, 205 218 )); 206 219 207 220 $dialog->setResizeX(true);
+86 -34
webroot/rsrc/js/core/behavior-object-selector.js
··· 10 10 var n = 0; 11 11 12 12 var phids = {}; 13 + var display = []; 14 + 13 15 var handles = config.handles; 14 16 for (var k in handles) { 15 17 phids[k] = true; 16 18 } 17 - var button_list = {}; 19 + 18 20 var query_timer = null; 19 21 var query_delay = 50; 20 22 ··· 41 43 return; 42 44 } 43 45 44 - var display = []; 45 - button_list = {}; 46 + display = []; 46 47 for (var k in r) { 47 48 handles[r[k].phid] = r[k]; 48 - display.push(renderHandle(r[k], true)); 49 + display.push({phid: r[k].phid}); 49 50 } 50 51 51 - if (!display.length) { 52 - display = renderNote('No results.'); 53 - } 54 - 55 - JX.DOM.setContent(JX.$(config.results), display); 52 + redrawList(true); 56 53 } 57 54 58 55 function redrawAttached() { 59 - var display = []; 56 + var attached = []; 60 57 61 58 for (var k in phids) { 62 - display.push(renderHandle(handles[k], false)); 59 + attached.push(renderHandle(handles[k], false).item); 63 60 } 64 61 65 - if (!display.length) { 66 - display = renderNote('Nothing attached.'); 62 + if (!attached.length) { 63 + attached = renderNote('Nothing attached.'); 67 64 } 68 65 69 - JX.DOM.setContent(JX.$(config.current), display); 66 + JX.DOM.setContent(JX.$(config.current), attached); 70 67 phid_input.value = JX.keys(phids).join(';'); 71 68 } 72 69 73 - function renderHandle(h, attach) { 70 + function redrawList(rebuild) { 71 + var ii; 72 + var content; 74 73 74 + if (rebuild) { 75 + if (display.length) { 76 + var handle; 77 + 78 + content = []; 79 + for (ii = 0; ii < display.length; ii++) { 80 + handle = handles[display[ii].phid]; 81 + 82 + display[ii].node = renderHandle(handle, true); 83 + content.push(display[ii].node.item); 84 + } 85 + } else { 86 + content = renderNote('No results.'); 87 + } 88 + 89 + JX.DOM.setContent(JX.$(config.results), content); 90 + } 91 + 92 + var phid; 93 + var is_disabled; 94 + var button; 95 + 96 + var at_maximum = !canSelectMore(); 97 + 98 + for (ii = 0; ii < display.length; ii++) { 99 + phid = display[ii].phid; 100 + 101 + is_disabled = false; 102 + 103 + // If this object is already selected, you can not select it again. 104 + if (phids.hasOwnProperty(phid)) { 105 + is_disabled = true; 106 + } 107 + 108 + // If the maximum number of objects are already selected, you can 109 + // not select more. 110 + if (at_maximum) { 111 + is_disabled = true; 112 + } 113 + 114 + button = display[ii].node.button; 115 + JX.DOM.alterClass(button, 'disabled', is_disabled); 116 + button.disabled = is_disabled; 117 + } 118 + 119 + } 120 + 121 + function renderHandle(h, attach) { 75 122 var some_icon = JX.$N( 76 123 'span', 77 124 {className: 'phui-icon-view phui-font-fa ' + ··· 111 158 meta: {handle: h, table:table}}, 112 159 cells)); 113 160 114 - if (attach) { 115 - button_list[h.phid] = select_object_button; 116 - if (h.phid in phids) { 117 - JX.DOM.alterClass(select_object_button, 'disabled', true); 118 - select_object_button.disabled = true; 119 - } 120 - } 121 - 122 - return table; 161 + return { 162 + item: table, 163 + button: select_object_button 164 + }; 123 165 } 124 166 125 167 function renderNote(note) { ··· 138 180 .send(); 139 181 } 140 182 183 + function canSelectMore() { 184 + if (!config.maximum) { 185 + return true; 186 + } 187 + 188 + if (JX.keys(phids).length < config.maximum) { 189 + return true; 190 + } 191 + 192 + return false; 193 + } 194 + 141 195 JX.DOM.listen( 142 196 JX.$(config.results), 143 197 'click', ··· 151 205 return; 152 206 } 153 207 208 + if (!canSelectMore()) { 209 + return; 210 + } 211 + 154 212 phids[phid] = true; 155 - JX.DOM.alterClass(button_list[phid], 'disabled', true); 156 - button_list[phid].disabled = true; 157 213 214 + redrawList(false); 158 215 redrawAttached(); 159 216 }); 160 217 ··· 170 227 171 228 delete phids[phid]; 172 229 173 - // NOTE: We may not have a button in the button list, if this result is 174 - // not visible in the current search results. 175 - if (button_list[phid]) { 176 - JX.DOM.alterClass(button_list[phid], 'disabled', false); 177 - button_list[phid].disabled = false; 178 - } 179 - 230 + redrawList(false); 180 231 redrawAttached(); 181 232 }); 182 233 ··· 205 256 }); 206 257 207 258 sendQuery(); 259 + 260 + redrawList(true); 208 261 redrawAttached(); 209 - 210 262 });