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

Replace all Maniphest commenting code with EditEngine commenting code

Summary:
Ref T9132. Like D14659, I'll hold this until after the cut.

This swaps commenting in Maniphest over to EditEngine / stackable actions. New code doesn't have parity yet, although none of the things we're missing should technically be //strictly mandatory//. There's a list inline. I'll restore these in the next diffs.

Briefly -- comments, subscribers and projects work. Status, owners and priority do not yet.

Test Plan:
- Made comments and added subscribers and projects.
- Read through the old code to look for missing features and tried to document them all.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9132

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

+20 -731
+1 -29
resources/celerity/map.php
··· 15 15 'diffusion.pkg.css' => 'f45955ed', 16 16 'diffusion.pkg.js' => 'ca1c8b5a', 17 17 'maniphest.pkg.css' => '4845691a', 18 - 'maniphest.pkg.js' => '3ec6a6d5', 18 + 'maniphest.pkg.js' => '949a7498', 19 19 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', 20 20 'rsrc/css/aphront/dark-console.css' => '6378ef3d', 21 21 'rsrc/css/aphront/dialog-view.css' => 'be0e3a46', ··· 407 407 'rsrc/js/application/maniphest/behavior-line-chart.js' => '88f0c5b3', 408 408 'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2', 409 409 'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '71237763', 410 - 'rsrc/js/application/maniphest/behavior-transaction-controls.js' => '44168bad', 411 - 'rsrc/js/application/maniphest/behavior-transaction-expand.js' => '5fefb143', 412 - 'rsrc/js/application/maniphest/behavior-transaction-preview.js' => '4c95d29e', 413 410 'rsrc/js/application/owners/OwnersPathEditor.js' => 'aa1733d0', 414 411 'rsrc/js/application/owners/owners-path-editor.js' => '7a68dda3', 415 412 'rsrc/js/application/passphrase/passphrase-credential-control.js' => '3cb0b2fc', ··· 624 621 'javelin-behavior-maniphest-batch-selector' => '7b98d7c5', 625 622 'javelin-behavior-maniphest-list-editor' => 'a9f88de2', 626 623 'javelin-behavior-maniphest-subpriority-editor' => '71237763', 627 - 'javelin-behavior-maniphest-transaction-controls' => '44168bad', 628 - 'javelin-behavior-maniphest-transaction-expand' => '5fefb143', 629 - 'javelin-behavior-maniphest-transaction-preview' => '4c95d29e', 630 624 'javelin-behavior-owners-path-editor' => '7a68dda3', 631 625 'javelin-behavior-passphrase-credential-control' => '3cb0b2fc', 632 626 'javelin-behavior-persona-login' => '9414ff18', ··· 1095 1089 'javelin-dom', 1096 1090 'javelin-request', 1097 1091 ), 1098 - '44168bad' => array( 1099 - 'javelin-behavior', 1100 - 'javelin-dom', 1101 - 'phabricator-prefab', 1102 - ), 1103 1092 '44959b73' => array( 1104 1093 'javelin-util', 1105 1094 'javelin-uri', ··· 1141 1130 'javelin-request', 1142 1131 'javelin-util', 1143 1132 ), 1144 - '4c95d29e' => array( 1145 - 'javelin-behavior', 1146 - 'javelin-dom', 1147 - 'javelin-util', 1148 - 'javelin-json', 1149 - 'javelin-stratcom', 1150 - 'phabricator-shaped-request', 1151 - ), 1152 1133 '4cebc641' => array( 1153 1134 'javelin-install', 1154 1135 ), ··· 1266 1247 'javelin-util', 1267 1248 'phabricator-prefab', 1268 1249 'javelin-json', 1269 - ), 1270 - '5fefb143' => array( 1271 - 'javelin-behavior', 1272 - 'javelin-dom', 1273 - 'javelin-workflow', 1274 - 'javelin-stratcom', 1275 1250 ), 1276 1251 60479091 => array( 1277 1252 'phabricator-busy', ··· 2291 2266 ), 2292 2267 'maniphest.pkg.js' => array( 2293 2268 'javelin-behavior-maniphest-batch-selector', 2294 - 'javelin-behavior-maniphest-transaction-controls', 2295 - 'javelin-behavior-maniphest-transaction-preview', 2296 - 'javelin-behavior-maniphest-transaction-expand', 2297 2269 'javelin-behavior-maniphest-subpriority-editor', 2298 2270 'javelin-behavior-maniphest-list-editor', 2299 2271 ),
-3
resources/celerity/packages.php
··· 187 187 ), 188 188 'maniphest.pkg.js' => array( 189 189 'javelin-behavior-maniphest-batch-selector', 190 - 'javelin-behavior-maniphest-transaction-controls', 191 - 'javelin-behavior-maniphest-transaction-preview', 192 - 'javelin-behavior-maniphest-transaction-expand', 193 190 'javelin-behavior-maniphest-subpriority-editor', 194 191 'javelin-behavior-maniphest-list-editor', 195 192 ),
-4
src/__phutil_library_map__.php
··· 1328 1328 'ManiphestTransaction' => 'applications/maniphest/storage/ManiphestTransaction.php', 1329 1329 'ManiphestTransactionComment' => 'applications/maniphest/storage/ManiphestTransactionComment.php', 1330 1330 'ManiphestTransactionEditor' => 'applications/maniphest/editor/ManiphestTransactionEditor.php', 1331 - 'ManiphestTransactionPreviewController' => 'applications/maniphest/controller/ManiphestTransactionPreviewController.php', 1332 1331 'ManiphestTransactionQuery' => 'applications/maniphest/query/ManiphestTransactionQuery.php', 1333 - 'ManiphestTransactionSaveController' => 'applications/maniphest/controller/ManiphestTransactionSaveController.php', 1334 1332 'ManiphestUpdateConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestUpdateConduitAPIMethod.php', 1335 1333 'ManiphestView' => 'applications/maniphest/view/ManiphestView.php', 1336 1334 'MetaMTAEmailTransactionCommand' => 'applications/metamta/command/MetaMTAEmailTransactionCommand.php', ··· 5320 5318 'ManiphestTransaction' => 'PhabricatorApplicationTransaction', 5321 5319 'ManiphestTransactionComment' => 'PhabricatorApplicationTransactionComment', 5322 5320 'ManiphestTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 5323 - 'ManiphestTransactionPreviewController' => 'ManiphestController', 5324 5321 'ManiphestTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 5325 - 'ManiphestTransactionSaveController' => 'ManiphestController', 5326 5322 'ManiphestUpdateConduitAPIMethod' => 'ManiphestConduitAPIMethod', 5327 5323 'ManiphestView' => 'AphrontView', 5328 5324 'MetaMTAEmailTransactionCommand' => 'Phobject',
+8 -217
src/applications/maniphest/controller/ManiphestTaskDetailController.php
··· 118 118 new ManiphestTransactionQuery(), 119 119 $engine); 120 120 121 - $resolution_types = ManiphestTaskStatus::getTaskStatusMap(); 122 - 123 - $transaction_types = array( 124 - PhabricatorTransactions::TYPE_COMMENT => pht('Comment'), 125 - ManiphestTransaction::TYPE_STATUS => pht('Change Status'), 126 - ManiphestTransaction::TYPE_OWNER => pht('Reassign / Claim'), 127 - PhabricatorTransactions::TYPE_SUBSCRIBERS => pht('Add CCs'), 128 - ManiphestTransaction::TYPE_PRIORITY => pht('Change Priority'), 129 - PhabricatorTransactions::TYPE_EDGE => pht('Associate Projects'), 130 - ); 131 - 132 - // Remove actions the user doesn't have permission to take. 133 - 134 - $requires = array( 135 - ManiphestTransaction::TYPE_OWNER => 136 - ManiphestEditAssignCapability::CAPABILITY, 137 - ManiphestTransaction::TYPE_PRIORITY => 138 - ManiphestEditPriorityCapability::CAPABILITY, 139 - PhabricatorTransactions::TYPE_EDGE => 140 - ManiphestEditProjectsCapability::CAPABILITY, 141 - ManiphestTransaction::TYPE_STATUS => 142 - ManiphestEditStatusCapability::CAPABILITY, 143 - ); 144 - 145 - foreach ($transaction_types as $type => $name) { 146 - if (isset($requires[$type])) { 147 - if (!$this->hasApplicationCapability($requires[$type])) { 148 - unset($transaction_types[$type]); 149 - } 150 - } 151 - } 152 - 153 - // Don't show an option to change to the current status, or to change to 154 - // the duplicate status explicitly. 155 - unset($resolution_types[$task->getStatus()]); 156 - unset($resolution_types[ManiphestTaskStatus::getDuplicateStatus()]); 157 - 158 - // Don't show owner/priority changes for closed tasks, as they don't make 159 - // much sense. 160 - if ($task->isClosed()) { 161 - unset($transaction_types[ManiphestTransaction::TYPE_PRIORITY]); 162 - unset($transaction_types[ManiphestTransaction::TYPE_OWNER]); 163 - } 164 - 165 - $default_claim = array( 166 - $viewer->getPHID() => $viewer->getUsername(). 167 - ' ('.$viewer->getRealName().')', 168 - ); 169 - 170 - $draft = id(new PhabricatorDraft())->loadOneWhere( 171 - 'authorPHID = %s AND draftKey = %s', 172 - $viewer->getPHID(), 173 - $task->getPHID()); 174 - if ($draft) { 175 - $draft_text = $draft->getDraft(); 176 - } else { 177 - $draft_text = null; 178 - } 179 - 180 - $projects_source = new PhabricatorProjectDatasource(); 181 - $users_source = new PhabricatorPeopleDatasource(); 182 - $mailable_source = new PhabricatorMetaMTAMailableDatasource(); 183 - 184 - $comment_form = new AphrontFormView(); 185 - $comment_form 186 - ->setUser($viewer) 187 - ->setWorkflow(true) 188 - ->setAction('/maniphest/transaction/save/') 189 - ->setEncType('multipart/form-data') 190 - ->addHiddenInput('taskID', $task->getID()) 191 - ->appendChild( 192 - id(new AphrontFormSelectControl()) 193 - ->setLabel(pht('Action')) 194 - ->setName('action') 195 - ->setOptions($transaction_types) 196 - ->setID('transaction-action')) 197 - ->appendChild( 198 - id(new AphrontFormSelectControl()) 199 - ->setLabel(pht('Status')) 200 - ->setName('resolution') 201 - ->setControlID('resolution') 202 - ->setControlStyle('display: none') 203 - ->setOptions($resolution_types)) 204 - ->appendControl( 205 - id(new AphrontFormTokenizerControl()) 206 - ->setLabel(pht('Assign To')) 207 - ->setName('assign_to') 208 - ->setControlID('assign_to') 209 - ->setControlStyle('display: none') 210 - ->setID('assign-tokenizer') 211 - ->setDisableBehavior(true) 212 - ->setDatasource($users_source)) 213 - ->appendControl( 214 - id(new AphrontFormTokenizerControl()) 215 - ->setLabel(pht('CCs')) 216 - ->setName('ccs') 217 - ->setControlID('ccs') 218 - ->setControlStyle('display: none') 219 - ->setID('cc-tokenizer') 220 - ->setDisableBehavior(true) 221 - ->setDatasource($mailable_source)) 222 - ->appendChild( 223 - id(new AphrontFormSelectControl()) 224 - ->setLabel(pht('Priority')) 225 - ->setName('priority') 226 - ->setOptions($priority_map) 227 - ->setControlID('priority') 228 - ->setControlStyle('display: none') 229 - ->setValue($task->getPriority())) 230 - ->appendControl( 231 - id(new AphrontFormTokenizerControl()) 232 - ->setLabel(pht('Projects')) 233 - ->setName('projects') 234 - ->setControlID('projects') 235 - ->setControlStyle('display: none') 236 - ->setID('projects-tokenizer') 237 - ->setDisableBehavior(true) 238 - ->setDatasource($projects_source)) 239 - ->appendChild( 240 - id(new AphrontFormFileControl()) 241 - ->setLabel(pht('File')) 242 - ->setName('file') 243 - ->setControlID('file') 244 - ->setControlStyle('display: none')) 245 - ->appendChild( 246 - id(new PhabricatorRemarkupControl()) 247 - ->setUser($viewer) 248 - ->setLabel(pht('Comments')) 249 - ->setName('comments') 250 - ->setValue($draft_text) 251 - ->setID('transaction-comments') 252 - ->setUser($viewer)) 253 - ->appendChild( 254 - id(new AphrontFormSubmitControl()) 255 - ->setValue(pht('Submit'))); 256 - 257 - $control_map = array( 258 - ManiphestTransaction::TYPE_STATUS => 'resolution', 259 - ManiphestTransaction::TYPE_OWNER => 'assign_to', 260 - PhabricatorTransactions::TYPE_SUBSCRIBERS => 'ccs', 261 - ManiphestTransaction::TYPE_PRIORITY => 'priority', 262 - PhabricatorTransactions::TYPE_EDGE => 'projects', 263 - ); 264 - 265 - $tokenizer_map = array( 266 - PhabricatorTransactions::TYPE_EDGE => array( 267 - 'id' => 'projects-tokenizer', 268 - 'src' => $projects_source->getDatasourceURI(), 269 - 'placeholder' => $projects_source->getPlaceholderText(), 270 - ), 271 - ManiphestTransaction::TYPE_OWNER => array( 272 - 'id' => 'assign-tokenizer', 273 - 'src' => $users_source->getDatasourceURI(), 274 - 'value' => $default_claim, 275 - 'limit' => 1, 276 - 'placeholder' => $users_source->getPlaceholderText(), 277 - ), 278 - PhabricatorTransactions::TYPE_SUBSCRIBERS => array( 279 - 'id' => 'cc-tokenizer', 280 - 'src' => $mailable_source->getDatasourceURI(), 281 - 'placeholder' => $mailable_source->getPlaceholderText(), 282 - ), 283 - ); 284 - 285 - // TODO: Initializing these behaviors for logged out users fatals things. 286 - if ($viewer->isLoggedIn()) { 287 - Javelin::initBehavior('maniphest-transaction-controls', array( 288 - 'select' => 'transaction-action', 289 - 'controlMap' => $control_map, 290 - 'tokenizers' => $tokenizer_map, 291 - )); 292 - 293 - Javelin::initBehavior('maniphest-transaction-preview', array( 294 - 'uri' => '/maniphest/transaction/preview/'.$task->getID().'/', 295 - 'preview' => 'transaction-preview', 296 - 'comments' => 'transaction-comments', 297 - 'action' => 'transaction-action', 298 - 'map' => $control_map, 299 - 'tokenizers' => $tokenizer_map, 300 - )); 301 - } 302 - 303 - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); 304 - $comment_header = $is_serious 305 - ? pht('Add Comment') 306 - : pht('Weigh In'); 307 - 308 - $preview_panel = phutil_tag_div( 309 - 'aphront-panel-preview', 310 - phutil_tag( 311 - 'div', 312 - array('id' => 'transaction-preview'), 313 - phutil_tag_div( 314 - 'aphront-panel-preview-loading-text', 315 - pht('Loading preview...')))); 316 - 317 - $object_name = 'T'.$task->getID(); 318 121 $actions = $this->buildActionView($task); 319 122 123 + $monogram = $task->getMonogram(); 320 124 $crumbs = $this->buildApplicationCrumbs() 321 - ->addTextCrumb($object_name, '/'.$object_name); 125 + ->addTextCrumb($monogram, '/'.$monogram); 322 126 323 127 $header = $this->buildHeaderView($task); 324 128 $properties = $this->buildPropertyView( 325 129 $task, $field_list, $edges, $actions, $handles); 326 130 $description = $this->buildDescriptionView($task, $engine); 327 131 328 - if (!$viewer->isLoggedIn()) { 329 - // TODO: Eventually, everything should run through this. For now, we're 330 - // only using it to get a consistent "Login to Comment" button. 331 - $comment_box = id(new PhabricatorApplicationTransactionCommentView()) 332 - ->setUser($viewer) 333 - ->setRequestURI($request->getRequestURI()); 334 - $preview_panel = null; 335 - } else { 336 - $comment_box = id(new PHUIObjectBoxView()) 337 - ->setFlush(true) 338 - ->setHeaderText($comment_header) 339 - ->setForm($comment_form); 340 - $timeline->setQuoteTargetID('transaction-comments'); 341 - $timeline->setQuoteRef($object_name); 342 - } 343 - 344 132 $object_box = id(new PHUIObjectBoxView()) 345 133 ->setHeader($header) 346 134 ->addPropertyList($properties); ··· 349 137 $object_box->addPropertyList($description); 350 138 } 351 139 352 - $title = 'T'.$task->getID().' '.$task->getTitle(); 140 + $title = pht('%s %s', $monogram, $task->getTitle()); 141 + 142 + $comment_view = id(new ManiphestEditEngine()) 143 + ->setViewer($viewer) 144 + ->buildEditEngineCommentView($task); 353 145 354 146 return $this->newPage() 355 147 ->setTitle($title) ··· 363 155 $info_view, 364 156 $object_box, 365 157 $timeline, 366 - $comment_box, 367 - $preview_panel, 158 + $comment_view, 368 159 )); 369 160 } 370 161
-128
src/applications/maniphest/controller/ManiphestTransactionPreviewController.php
··· 1 - <?php 2 - 3 - final class ManiphestTransactionPreviewController extends ManiphestController { 4 - 5 - public function handleRequest(AphrontRequest $request) { 6 - $viewer = $this->getViewer(); 7 - $id = $request->getURIData('id'); 8 - 9 - $comments = $request->getStr('comments'); 10 - 11 - $task = id(new ManiphestTaskQuery()) 12 - ->setViewer($viewer) 13 - ->withIDs(array($id)) 14 - ->executeOne(); 15 - if (!$task) { 16 - return new Aphront404Response(); 17 - } 18 - 19 - id(new PhabricatorDraft()) 20 - ->setAuthorPHID($viewer->getPHID()) 21 - ->setDraftKey($task->getPHID()) 22 - ->setDraft($comments) 23 - ->replaceOrDelete(); 24 - 25 - $action = $request->getStr('action'); 26 - 27 - $transaction = new ManiphestTransaction(); 28 - $transaction->setAuthorPHID($viewer->getPHID()); 29 - $transaction->setTransactionType($action); 30 - 31 - // This should really be split into a separate transaction, but it should 32 - // all come out in the wash once we fully move to modern stuff. 33 - $transaction->attachComment( 34 - id(new ManiphestTransactionComment()) 35 - ->setContent($comments)); 36 - 37 - $value = $request->getStr('value'); 38 - // grab phids for handles and set transaction values based on action and 39 - // value (empty or control-specific format) coming in from the wire 40 - switch ($action) { 41 - case ManiphestTransaction::TYPE_PRIORITY: 42 - $transaction->setOldValue($task->getPriority()); 43 - $transaction->setNewValue($value); 44 - break; 45 - case ManiphestTransaction::TYPE_OWNER: 46 - if ($value) { 47 - $value = current(json_decode($value)); 48 - $phids = array($value); 49 - } else { 50 - $phids = array(); 51 - } 52 - $transaction->setNewValue($value); 53 - break; 54 - case PhabricatorTransactions::TYPE_SUBSCRIBERS: 55 - if ($value) { 56 - $value = json_decode($value); 57 - } 58 - if (!$value) { 59 - $value = array(); 60 - } 61 - $phids = array(); 62 - foreach ($value as $cc_phid) { 63 - $phids[] = $cc_phid; 64 - } 65 - $transaction->setOldValue(array()); 66 - $transaction->setNewValue($phids); 67 - break; 68 - case PhabricatorTransactions::TYPE_EDGE: 69 - if ($value) { 70 - $value = phutil_json_decode($value); 71 - } 72 - if (!$value) { 73 - $value = array(); 74 - } 75 - 76 - $phids = array(); 77 - $value = array_fuse($value); 78 - foreach ($value as $project_phid) { 79 - $phids[] = $project_phid; 80 - $value[$project_phid] = array('dst' => $project_phid); 81 - } 82 - 83 - $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 84 - $transaction 85 - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 86 - ->setMetadataValue('edge:type', $project_type) 87 - ->setOldValue(array()) 88 - ->setNewValue($value); 89 - break; 90 - case ManiphestTransaction::TYPE_STATUS: 91 - $phids = array(); 92 - $transaction->setOldValue($task->getStatus()); 93 - $transaction->setNewValue($value); 94 - break; 95 - default: 96 - $phids = array(); 97 - $transaction->setNewValue($value); 98 - break; 99 - } 100 - $phids[] = $viewer->getPHID(); 101 - 102 - $handles = $this->loadViewerHandles($phids); 103 - 104 - $transactions = array(); 105 - $transactions[] = $transaction; 106 - 107 - $engine = new PhabricatorMarkupEngine(); 108 - $engine->setViewer($viewer); 109 - $engine->setContextObject($task); 110 - if ($transaction->hasComment()) { 111 - $engine->addObject( 112 - $transaction->getComment(), 113 - PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); 114 - } 115 - $engine->process(); 116 - 117 - $transaction->setHandles($handles); 118 - 119 - $view = id(new PhabricatorApplicationTransactionView()) 120 - ->setUser($viewer) 121 - ->setTransactions($transactions) 122 - ->setIsPreview(true); 123 - 124 - return id(new AphrontAjaxResponse()) 125 - ->setContent((string)phutil_implode_html('', $view->buildEvents())); 126 - } 127 - 128 - }
-209
src/applications/maniphest/controller/ManiphestTransactionSaveController.php
··· 1 - <?php 2 - 3 - final class ManiphestTransactionSaveController extends ManiphestController { 4 - 5 - public function handleRequest(AphrontRequest $request) { 6 - $viewer = $this->getViewer(); 7 - 8 - $task = id(new ManiphestTaskQuery()) 9 - ->setViewer($viewer) 10 - ->withIDs(array($request->getStr('taskID'))) 11 - ->needSubscriberPHIDs(true) 12 - ->needProjectPHIDs(true) 13 - ->executeOne(); 14 - if (!$task) { 15 - return new Aphront404Response(); 16 - } 17 - 18 - $task_uri = '/'.$task->getMonogram(); 19 - 20 - $transactions = array(); 21 - 22 - $action = $request->getStr('action'); 23 - 24 - $implicit_ccs = array(); 25 - $explicit_ccs = array(); 26 - 27 - $transaction = new ManiphestTransaction(); 28 - $transaction 29 - ->setTransactionType($action); 30 - 31 - switch ($action) { 32 - case ManiphestTransaction::TYPE_STATUS: 33 - $transaction->setNewValue($request->getStr('resolution')); 34 - break; 35 - case ManiphestTransaction::TYPE_OWNER: 36 - $assign_to = $request->getArr('assign_to'); 37 - $assign_to = reset($assign_to); 38 - $transaction->setNewValue($assign_to); 39 - break; 40 - case PhabricatorTransactions::TYPE_EDGE: 41 - $projects = $request->getArr('projects'); 42 - $projects = array_merge($projects, $task->getProjectPHIDs()); 43 - $projects = array_filter($projects); 44 - $projects = array_unique($projects); 45 - 46 - $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 47 - $transaction 48 - ->setMetadataValue('edge:type', $project_type) 49 - ->setNewValue( 50 - array( 51 - '+' => array_fuse($projects), 52 - )); 53 - break; 54 - case PhabricatorTransactions::TYPE_SUBSCRIBERS: 55 - // Accumulate the new explicit CCs into the array that we'll add in 56 - // the CC transaction later. 57 - $explicit_ccs = $request->getArr('ccs'); 58 - 59 - // Throw away the primary transaction. 60 - $transaction = null; 61 - break; 62 - case ManiphestTransaction::TYPE_PRIORITY: 63 - $transaction->setNewValue($request->getInt('priority')); 64 - break; 65 - case PhabricatorTransactions::TYPE_COMMENT: 66 - // Nuke this, we're going to create it below. 67 - $transaction = null; 68 - break; 69 - default: 70 - throw new Exception(pht("Unknown action '%s'!", $action)); 71 - } 72 - 73 - if ($transaction) { 74 - $transactions[] = $transaction; 75 - } 76 - 77 - 78 - // When you interact with a task, we add you to the CC list so you get 79 - // further updates, and possibly assign the task to you if you took an 80 - // ownership action (closing it) but it's currently unowned. We also move 81 - // previous owners to CC if ownership changes. Detect all these conditions 82 - // and create side-effect transactions for them. 83 - 84 - $implicitly_claimed = false; 85 - if ($action == ManiphestTransaction::TYPE_OWNER) { 86 - if ($task->getOwnerPHID() == $transaction->getNewValue()) { 87 - // If this is actually no-op, don't generate the side effect. 88 - } else { 89 - // Otherwise, when a task is reassigned, move the previous owner to CC. 90 - if ($task->getOwnerPHID()) { 91 - $implicit_ccs[] = $task->getOwnerPHID(); 92 - } 93 - } 94 - } 95 - 96 - if ($action == ManiphestTransaction::TYPE_STATUS) { 97 - $resolution = $request->getStr('resolution'); 98 - if (!$task->getOwnerPHID() && 99 - ManiphestTaskStatus::isClosedStatus($resolution)) { 100 - // Closing an unassigned task. Assign the user as the owner of 101 - // this task. 102 - $assign = new ManiphestTransaction(); 103 - $assign->setTransactionType(ManiphestTransaction::TYPE_OWNER); 104 - $assign->setNewValue($viewer->getPHID()); 105 - $transactions[] = $assign; 106 - 107 - $implicitly_claimed = true; 108 - } 109 - } 110 - 111 - $user_owns_task = false; 112 - if ($implicitly_claimed) { 113 - $user_owns_task = true; 114 - } else { 115 - if ($action == ManiphestTransaction::TYPE_OWNER) { 116 - if ($transaction->getNewValue() == $viewer->getPHID()) { 117 - $user_owns_task = true; 118 - } 119 - } else if ($task->getOwnerPHID() == $viewer->getPHID()) { 120 - $user_owns_task = true; 121 - } 122 - } 123 - 124 - if (!$user_owns_task) { 125 - // If we aren't making the user the new task owner and they aren't the 126 - // existing task owner, add them to CC unless they're aleady CC'd. 127 - if (!in_array($viewer->getPHID(), $task->getSubscriberPHIDs())) { 128 - $implicit_ccs[] = $viewer->getPHID(); 129 - } 130 - } 131 - 132 - if ($implicit_ccs || $explicit_ccs) { 133 - 134 - // TODO: These implicit CC rules should probably be handled inside the 135 - // Editor, eventually. 136 - 137 - $all_ccs = array_fuse($implicit_ccs) + array_fuse($explicit_ccs); 138 - 139 - $cc_transaction = id(new ManiphestTransaction()) 140 - ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) 141 - ->setNewValue(array('+' => $all_ccs)); 142 - 143 - if (!$explicit_ccs) { 144 - $cc_transaction->setIgnoreOnNoEffect(true); 145 - } 146 - 147 - $transactions[] = $cc_transaction; 148 - } 149 - 150 - $comments = $request->getStr('comments'); 151 - if (strlen($comments) || !$transactions) { 152 - $transactions[] = id(new ManiphestTransaction()) 153 - ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) 154 - ->attachComment( 155 - id(new ManiphestTransactionComment()) 156 - ->setContent($comments)); 157 - } 158 - 159 - $event = new PhabricatorEvent( 160 - PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK, 161 - array( 162 - 'task' => $task, 163 - 'new' => false, 164 - 'transactions' => $transactions, 165 - )); 166 - $event->setUser($viewer); 167 - $event->setAphrontRequest($request); 168 - PhutilEventEngine::dispatchEvent($event); 169 - 170 - $task = $event->getValue('task'); 171 - $transactions = $event->getValue('transactions'); 172 - 173 - $editor = id(new ManiphestTransactionEditor()) 174 - ->setActor($viewer) 175 - ->setContentSourceFromRequest($request) 176 - ->setContinueOnMissingFields(true) 177 - ->setContinueOnNoEffect($request->isContinueRequest()); 178 - 179 - try { 180 - $editor->applyTransactions($task, $transactions); 181 - } catch (PhabricatorApplicationTransactionNoEffectException $ex) { 182 - return id(new PhabricatorApplicationTransactionNoEffectResponse()) 183 - ->setCancelURI($task_uri) 184 - ->setException($ex); 185 - } 186 - 187 - $draft = id(new PhabricatorDraft())->loadOneWhere( 188 - 'authorPHID = %s AND draftKey = %s', 189 - $viewer->getPHID(), 190 - $task->getPHID()); 191 - if ($draft) { 192 - $draft->delete(); 193 - } 194 - 195 - $event = new PhabricatorEvent( 196 - PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK, 197 - array( 198 - 'task' => $task, 199 - 'new' => false, 200 - 'transactions' => $transactions, 201 - )); 202 - $event->setUser($viewer); 203 - $event->setAphrontRequest($request); 204 - PhutilEventEngine::dispatchEvent($event); 205 - 206 - return id(new AphrontRedirectResponse())->setURI($task_uri); 207 - } 208 - 209 - }
+11 -1
src/applications/maniphest/editor/ManiphestEditEngine.php
··· 67 67 68 68 $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); 69 69 70 + // TODO: Restore these or toss them: 71 + // - Require a single owner. 72 + // - Default owner to viewer. 73 + // - Don't show "change status" for closed tasks. 74 + // - Don't show "change owner" for closed tasks. 75 + // - Don't let users change a task status to "Duplicate". 76 + // - Make sure "Quote" works. 77 + // - When closing an unassigned task, assign the closing user. 78 + // - Make sure implicit CCs on actions are working reasonably. 79 + 70 80 return array( 71 81 id(new PhabricatorTextEditField()) 72 82 ->setKey('title') 73 83 ->setLabel(pht('Title')) 74 - ->setDescription(pht('Name of the paste.')) 84 + ->setDescription(pht('Name of the task.')) 75 85 ->setTransactionType(ManiphestTransaction::TYPE_TITLE) 76 86 ->setIsRequired(true) 77 87 ->setValue($object->getTitle()),
-35
webroot/rsrc/js/application/maniphest/behavior-transaction-controls.js
··· 1 - /** 2 - * @provides javelin-behavior-maniphest-transaction-controls 3 - * @requires javelin-behavior 4 - * javelin-dom 5 - * phabricator-prefab 6 - */ 7 - 8 - JX.behavior('maniphest-transaction-controls', function(config) { 9 - 10 - var tokenizers = {}; 11 - 12 - for (var k in config.tokenizers) { 13 - var tconfig = config.tokenizers[k]; 14 - tokenizers[k] = JX.Prefab.buildTokenizer(tconfig).tokenizer; 15 - tokenizers[k].start(); 16 - } 17 - 18 - JX.DOM.listen( 19 - JX.$(config.select), 20 - 'change', 21 - null, 22 - function() { 23 - for (var k in config.controlMap) { 24 - if (k == JX.$(config.select).value) { 25 - JX.DOM.show(JX.$(config.controlMap[k])); 26 - if (tokenizers[k]) { 27 - tokenizers[k].refresh(); 28 - } 29 - } else { 30 - JX.DOM.hide(JX.$(config.controlMap[k])); 31 - } 32 - } 33 - }); 34 - 35 - });
-29
webroot/rsrc/js/application/maniphest/behavior-transaction-expand.js
··· 1 - /** 2 - * @provides javelin-behavior-maniphest-transaction-expand 3 - * @requires javelin-behavior 4 - * javelin-dom 5 - * javelin-workflow 6 - * javelin-stratcom 7 - */ 8 - 9 - /** 10 - * When the user clicks "show details" in a Maniphest transaction, replace the 11 - * summary rendering with a detailed rendering. 12 - */ 13 - JX.behavior('maniphest-transaction-expand', function() { 14 - 15 - JX.Stratcom.listen( 16 - 'click', 17 - 'maniphest-expand-transaction', 18 - function(e) { 19 - e.kill(); 20 - JX.Workflow.newFromLink(e.getTarget(), {}) 21 - .setHandler(function(r) { 22 - JX.DOM.setContent( 23 - e.getNode('maniphest-transaction-description'), 24 - JX.$H(r)); 25 - }) 26 - .start(); 27 - }); 28 - 29 - });
-76
webroot/rsrc/js/application/maniphest/behavior-transaction-preview.js
··· 1 - /** 2 - * @provides javelin-behavior-maniphest-transaction-preview 3 - * @requires javelin-behavior 4 - * javelin-dom 5 - * javelin-util 6 - * javelin-json 7 - * javelin-stratcom 8 - * phabricator-shaped-request 9 - */ 10 - 11 - JX.behavior('maniphest-transaction-preview', function(config) { 12 - 13 - var comments = JX.$(config.comments); 14 - var action = JX.$(config.action); 15 - 16 - var callback = function(r) { 17 - var panel = JX.$(config.preview); 18 - var data = getdata(); 19 - var hide = true; 20 - for (var field in data) { 21 - if (field == 'action') { 22 - continue; 23 - } 24 - if (data[field]) { 25 - hide = false; 26 - } 27 - } 28 - if (hide) { 29 - JX.DOM.hide(panel); 30 - } else { 31 - JX.DOM.setContent(panel, JX.$H(r)); 32 - JX.DOM.show(panel); 33 - } 34 - }; 35 - 36 - var getdata = function() { 37 - var selected = action.value; 38 - 39 - var value = null; 40 - try { 41 - var control = JX.$(config.map[selected]); 42 - var input = ([] 43 - .concat(JX.DOM.scry(control, 'select')) 44 - .concat(JX.DOM.scry(control, 'input')))[0]; 45 - if (JX.DOM.isType(input, 'input')) { 46 - // Avoid reading 'value'(s) out of the tokenizer free text input. 47 - if (input.type != 'hidden') { 48 - value = null; 49 - // Get the tokenizer and all that delicious data 50 - } else { 51 - var tokenizer_dom = JX.$(config.tokenizers[selected].id); 52 - var tokenizer = JX.Stratcom.getData(tokenizer_dom).tokenizer; 53 - value = JX.JSON.stringify(JX.keys(tokenizer.getTokens())); 54 - } 55 - } else { 56 - value = input.value; 57 - } 58 - } catch (_ignored_) { 59 - // Ignored. 60 - } 61 - 62 - return { 63 - comments : comments.value, 64 - action : selected, 65 - value : value || '' 66 - }; 67 - }; 68 - 69 - var request = new JX.PhabricatorShapedRequest(config.uri, callback, getdata); 70 - var trigger = JX.bind(request, request.trigger); 71 - 72 - JX.DOM.listen(comments, 'keydown', null, trigger); 73 - JX.DOM.listen(action, 'change', null, trigger); 74 - 75 - request.start(); 76 - });