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

Define bulk edits in terms of EditEngine, not hard-coded ad-hoc definitions

Summary:
Depends on D18862. See PHI173. Ref T13025. Fixes T10005. This redefines bulk edits in terms of EditEngine fields, rather than hard-coding the whole thing.

Only text fields -- and, specifically, only the "Title" field -- are supported after this change. Followup changes will add more bulk edit parameter types and broader field support.

However, the title field now works without any Maniphest-specific code, outside of the small amount of binding code in the `ManiphestBulkEditor` subclass.

Test Plan: Used the bulk edit workflow to change the titles of tasks.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13025, T10005

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

+449 -282
+19 -18
resources/celerity/map.php
··· 81 81 'rsrc/css/application/harbormaster/harbormaster.css' => 'f491c9f4', 82 82 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', 83 83 'rsrc/css/application/herald/herald.css' => 'cd8d0134', 84 - 'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5', 85 84 'rsrc/css/application/maniphest/report.css' => '9b9580b7', 86 85 'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b', 87 86 'rsrc/css/application/maniphest/task-summary.css' => '11cc5344', ··· 143 142 'rsrc/css/phui/phui-basic-nav-view.css' => '98c11ab3', 144 143 'rsrc/css/phui/phui-big-info-view.css' => 'acc3492c', 145 144 'rsrc/css/phui/phui-box.css' => '4bd6cdb9', 145 + 'rsrc/css/phui/phui-bulk-editor.css' => '1fe728a8', 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', ··· 419 419 'rsrc/js/application/herald/HeraldRuleEditor.js' => '2dff5579', 420 420 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 421 421 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 422 - 'rsrc/js/application/maniphest/behavior-batch-editor.js' => '782ab6e7', 423 422 'rsrc/js/application/maniphest/behavior-batch-selector.js' => 'ad54037e', 424 423 'rsrc/js/application/maniphest/behavior-line-chart.js' => 'e4232876', 425 424 'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2', ··· 477 476 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 478 477 'rsrc/js/core/behavior-autofocus.js' => '7319e029', 479 478 'rsrc/js/core/behavior-badge-view.js' => '8ff5e24c', 479 + 'rsrc/js/core/behavior-bulk-editor.js' => '5e178556', 480 480 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', 481 481 'rsrc/js/core/behavior-copy.js' => 'b0b8f86d', 482 482 'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96', ··· 532 532 'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac', 533 533 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03', 534 534 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', 535 - 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', 535 + 'rsrc/js/phuix/PHUIXFormControl.js' => '68bb05aa', 536 536 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', 537 537 ), 538 538 'symbols' => array( ··· 595 595 'javelin-behavior-audio-source' => '59b251eb', 596 596 'javelin-behavior-audit-preview' => 'd835b03a', 597 597 'javelin-behavior-badge-view' => '8ff5e24c', 598 + 'javelin-behavior-bulk-editor' => '5e178556', 598 599 'javelin-behavior-bulk-job-reload' => 'edf8a145', 599 600 'javelin-behavior-calendar-month-view' => 'fe33e256', 600 601 'javelin-behavior-choose-control' => '327a00d1', ··· 642 643 'javelin-behavior-lightbox-attachments' => '560f41da', 643 644 'javelin-behavior-line-chart' => 'e4232876', 644 645 'javelin-behavior-load-blame' => '42126667', 645 - 'javelin-behavior-maniphest-batch-editor' => '782ab6e7', 646 646 'javelin-behavior-maniphest-batch-selector' => 'ad54037e', 647 647 'javelin-behavior-maniphest-list-editor' => 'a9f88de2', 648 648 'javelin-behavior-maniphest-subpriority-editor' => '71237763', ··· 756 756 'javelin-workboard-column' => '758b4758', 757 757 'javelin-workboard-controller' => '26167537', 758 758 'javelin-workflow' => '1e911d0f', 759 - 'maniphest-batch-editor' => 'b0f0b6d5', 760 759 'maniphest-report-css' => '9b9580b7', 761 760 'maniphest-task-edit-css' => 'fda62a9b', 762 761 'maniphest-task-summary-css' => '11cc5344', ··· 823 822 'phui-basic-nav-view-css' => '98c11ab3', 824 823 'phui-big-info-view-css' => 'acc3492c', 825 824 'phui-box-css' => '4bd6cdb9', 825 + 'phui-bulk-editor-css' => '1fe728a8', 826 826 'phui-button-bar-css' => 'f1ff5494', 827 827 'phui-button-css' => '1863cc6e', 828 828 'phui-button-simple-css' => '8e1baf68', ··· 884 884 'phuix-autocomplete' => 'e0731603', 885 885 'phuix-button-view' => '8a91e1ac', 886 886 'phuix-dropdown-menu' => '04b2ae03', 887 - 'phuix-form-control-view' => '83e03671', 887 + 'phuix-form-control-view' => '68bb05aa', 888 888 'phuix-icon-view' => 'bff6884b', 889 889 'policy-css' => '957ea14c', 890 890 'policy-edit-css' => '815c66f7', ··· 1387 1387 'javelin-stratcom', 1388 1388 'javelin-dom', 1389 1389 ), 1390 + '5e178556' => array( 1391 + 'javelin-behavior', 1392 + 'javelin-dom', 1393 + 'javelin-util', 1394 + 'phabricator-prefab', 1395 + 'multirow-row-manager', 1396 + 'javelin-json', 1397 + 'phuix-form-control-view', 1398 + ), 1390 1399 '5e2634b9' => array( 1391 1400 'javelin-behavior', 1392 1401 'javelin-aphlict', ··· 1435 1444 'javelin-install', 1436 1445 'javelin-dom', 1437 1446 'phuix-button-view', 1447 + ), 1448 + '68bb05aa' => array( 1449 + 'javelin-install', 1450 + 'javelin-dom', 1438 1451 ), 1439 1452 '69adf288' => array( 1440 1453 'javelin-install', ··· 1524 1537 'javelin-request', 1525 1538 'javelin-util', 1526 1539 ), 1527 - '782ab6e7' => array( 1528 - 'javelin-behavior', 1529 - 'javelin-dom', 1530 - 'javelin-util', 1531 - 'phabricator-prefab', 1532 - 'multirow-row-manager', 1533 - 'javelin-json', 1534 - ), 1535 1540 '7927a7d3' => array( 1536 1541 'javelin-behavior', 1537 1542 'javelin-quicksand', ··· 1569 1574 '834a1173' => array( 1570 1575 'javelin-behavior', 1571 1576 'javelin-scrollbar', 1572 - ), 1573 - '83e03671' => array( 1574 - 'javelin-install', 1575 - 'javelin-dom', 1576 1577 ), 1577 1578 '8499b6ab' => array( 1578 1579 'javelin-behavior',
+4
src/__phutil_library_map__.php
··· 222 222 'AuditConduitAPIMethod' => 'applications/audit/conduit/AuditConduitAPIMethod.php', 223 223 'AuditQueryConduitAPIMethod' => 'applications/audit/conduit/AuditQueryConduitAPIMethod.php', 224 224 'AuthManageProvidersCapability' => 'applications/auth/capability/AuthManageProvidersCapability.php', 225 + 'BulkParameterType' => 'applications/transactions/bulk/type/BulkParameterType.php', 226 + 'BulkStringParameterType' => 'applications/transactions/bulk/type/BulkStringParameterType.php', 225 227 'CalendarTimeUtil' => 'applications/calendar/util/CalendarTimeUtil.php', 226 228 'CalendarTimeUtilTestCase' => 'applications/calendar/__tests__/CalendarTimeUtilTestCase.php', 227 229 'CelerityAPI' => 'applications/celerity/CelerityAPI.php', ··· 5242 5244 'AuditConduitAPIMethod' => 'ConduitAPIMethod', 5243 5245 'AuditQueryConduitAPIMethod' => 'AuditConduitAPIMethod', 5244 5246 'AuthManageProvidersCapability' => 'PhabricatorPolicyCapability', 5247 + 'BulkParameterType' => 'Phobject', 5248 + 'BulkStringParameterType' => 'BulkParameterType', 5245 5249 'CalendarTimeUtil' => 'Phobject', 5246 5250 'CalendarTimeUtilTestCase' => 'PhabricatorTestCase', 5247 5251 'CelerityAPI' => 'Phobject',
+4
src/applications/maniphest/bulk/ManiphestTaskBulkEngine.php
··· 18 18 return new ManiphestTaskSearchEngine(); 19 19 } 20 20 21 + public function newEditEngine() { 22 + return new ManiphestEditEngine(); 23 + } 24 + 21 25 public function getDoneURI() { 22 26 $board_uri = $this->getBoardURI(); 23 27 if ($board_uri) {
+1
src/applications/maniphest/editor/ManiphestEditEngine.php
··· 178 178 id(new PhabricatorTextEditField()) 179 179 ->setKey('title') 180 180 ->setLabel(pht('Title')) 181 + ->setBulkEditLabel(pht('Set title to')) 181 182 ->setDescription(pht('Name of the task.')) 182 183 ->setConduitDescription(pht('Rename the task.')) 183 184 ->setConduitTypeDescription(pht('New task name.'))
+66 -88
src/applications/transactions/bulk/PhabricatorBulkEngine.php
··· 10 10 private $editableList; 11 11 private $targetList; 12 12 13 + private $rootFormID; 14 + 13 15 abstract public function newSearchEngine(); 16 + abstract public function newEditEngine(); 14 17 15 18 public function getCancelURI() { 16 19 $saved_query = $this->savedQuery; ··· 118 121 array( 119 122 'action' => $this->getBulkURI(), 120 123 'method' => 'POST', 121 - 'id' => 'maniphest-batch-edit-form', 124 + 'id' => $this->getRootFormID(), 122 125 ), 123 126 array( 124 127 $this->newContextInputs(), ··· 290 293 291 294 private function newBulkActionForm() { 292 295 $viewer = $this->getViewer(); 296 + $input_id = celerity_generate_unique_node_id(); 293 297 294 - $cancel_uri = $this->getCancelURI(); 298 + $edit_engine = id($this->newEditEngine()) 299 + ->setViewer($viewer); 295 300 296 - $template = new AphrontTokenizerTemplateView(); 297 - $template = $template->render(); 301 + $edit_map = $edit_engine->newBulkEditMap(); 298 302 299 - $projects_source = new PhabricatorProjectDatasource(); 300 - $mailable_source = new PhabricatorMetaMTAMailableDatasource(); 301 - $mailable_source->setViewer($viewer); 302 - $owner_source = new ManiphestAssigneeDatasource(); 303 - $owner_source->setViewer($viewer); 304 - $spaces_source = id(new PhabricatorSpacesNamespaceDatasource()) 305 - ->setViewer($viewer); 306 - 307 - require_celerity_resource('maniphest-batch-editor'); 303 + require_celerity_resource('phui-bulk-editor-css'); 308 304 309 305 Javelin::initBehavior( 310 - 'maniphest-batch-editor', 306 + 'bulk-editor', 311 307 array( 312 - 'root' => 'maniphest-batch-edit-form', 313 - 'tokenizerTemplate' => $template, 314 - 'sources' => array( 315 - 'project' => array( 316 - 'src' => $projects_source->getDatasourceURI(), 317 - 'placeholder' => $projects_source->getPlaceholderText(), 318 - 'browseURI' => $projects_source->getBrowseURI(), 319 - ), 320 - 'owner' => array( 321 - 'src' => $owner_source->getDatasourceURI(), 322 - 'placeholder' => $owner_source->getPlaceholderText(), 323 - 'browseURI' => $owner_source->getBrowseURI(), 324 - 'limit' => 1, 325 - ), 326 - 'cc' => array( 327 - 'src' => $mailable_source->getDatasourceURI(), 328 - 'placeholder' => $mailable_source->getPlaceholderText(), 329 - 'browseURI' => $mailable_source->getBrowseURI(), 330 - ), 331 - 'spaces' => array( 332 - 'src' => $spaces_source->getDatasourceURI(), 333 - 'placeholder' => $spaces_source->getPlaceholderText(), 334 - 'browseURI' => $spaces_source->getBrowseURI(), 335 - 'limit' => 1, 336 - ), 337 - ), 338 - 'input' => 'batch-form-actions', 339 - 'priorityMap' => ManiphestTaskPriority::getTaskPriorityMap(), 340 - 'statusMap' => ManiphestTaskStatus::getTaskStatusMap(), 308 + 'rootNodeID' => $this->getRootFormID(), 309 + 'inputNodeID' => $input_id, 310 + 'edits' => $edit_map, 341 311 )); 342 312 343 - $form = id(new PHUIFormLayoutView()) 344 - ->setUser($viewer); 345 - 346 - $form->appendChild( 347 - phutil_tag( 348 - 'input', 349 - array( 350 - 'type' => 'hidden', 351 - 'name' => 'actions', 352 - 'id' => 'batch-form-actions', 353 - ))); 313 + $cancel_uri = $this->getCancelURI(); 354 314 355 - $form->appendChild( 356 - id(new PHUIFormInsetView()) 357 - ->setTitle(pht('Bulk Edit Actions')) 358 - ->setRightButton( 359 - javelin_tag( 360 - 'a', 361 - array( 362 - 'href' => '#', 363 - 'class' => 'button button-green', 364 - 'sigil' => 'add-action', 365 - 'mustcapture' => true, 366 - ), 367 - pht('Add Another Action'))) 368 - ->setContent( 369 - javelin_tag( 370 - 'table', 371 - array( 372 - 'sigil' => 'maniphest-batch-actions', 373 - 'class' => 'maniphest-batch-actions-table', 374 - ), 375 - ''))) 315 + return id(new PHUIFormLayoutView()) 316 + ->setViewer($viewer) 317 + ->appendChild( 318 + phutil_tag( 319 + 'input', 320 + array( 321 + 'type' => 'hidden', 322 + 'name' => 'xactions', 323 + 'id' => $input_id, 324 + ))) 325 + ->appendChild( 326 + id(new PHUIFormInsetView()) 327 + ->setTitle(pht('Bulk Edit Actions')) 328 + ->setRightButton( 329 + javelin_tag( 330 + 'a', 331 + array( 332 + 'href' => '#', 333 + 'class' => 'button button-green', 334 + 'sigil' => 'add-action', 335 + 'mustcapture' => true, 336 + ), 337 + pht('Add Another Action'))) 338 + ->setContent( 339 + javelin_tag( 340 + 'table', 341 + array( 342 + 'sigil' => 'bulk-actions', 343 + 'class' => 'bulk-edit-table', 344 + ), 345 + ''))) 376 346 ->appendChild( 377 347 id(new AphrontFormSubmitControl()) 378 348 ->setValue(pht('Apply Bulk Edit')) 379 349 ->addCancelButton($cancel_uri)); 380 - 381 - return $form; 382 350 } 383 351 384 352 private function buildEditResponse() { ··· 405 373 'You have not selected any objects to edit.')); 406 374 } 407 375 408 - $raw_actions = $request->getStr('actions'); 409 - if ($raw_actions) { 410 - $actions = phutil_json_decode($raw_actions); 376 + $raw_xactions = $request->getStr('xactions'); 377 + if ($raw_xactions) { 378 + $raw_xactions = phutil_json_decode($raw_xactions); 411 379 } else { 412 - $actions = array(); 380 + $raw_xactions = array(); 413 381 } 414 382 415 - if (!$actions) { 383 + if (!$raw_xactions) { 416 384 throw new Exception( 417 385 pht( 418 386 'You have not chosen any edits to apply.')); 419 387 } 420 388 389 + $edit_engine = id($this->newEditEngine()) 390 + ->setViewer($viewer); 391 + 392 + $xactions = $edit_engine->newRawBulkTransactions($raw_xactions); 393 + 421 394 $cancel_uri = $this->getCancelURI(); 422 395 $done_uri = $this->getDoneURI(); 423 396 424 397 $job = PhabricatorWorkerBulkJob::initializeNewJob( 425 398 $viewer, 426 - // TODO: This is a Maniphest-specific job type for now, but will become 427 - // a generic one so it gets to live here for now instead of in the task 428 - // specific BulkEngine subclass. 429 - new ManiphestTaskEditBulkJobType(), 399 + new PhabricatorEditEngineBulkJobType(), 430 400 array( 431 - 'taskPHIDs' => mpull($objects, 'getPHID'), 432 - 'actions' => $actions, 401 + 'objectPHIDs' => mpull($objects, 'getPHID'), 402 + 'xactions' => $xactions, 433 403 'cancelURI' => $cancel_uri, 434 404 'doneURI' => $done_uri, 435 405 )); ··· 449 419 450 420 return id(new AphrontRedirectResponse()) 451 421 ->setURI($job->getMonitorURI()); 422 + } 423 + 424 + private function getRootFormID() { 425 + if (!$this->rootFormID) { 426 + $this->rootFormID = celerity_generate_unique_node_id(); 427 + } 428 + 429 + return $this->rootFormID; 452 430 } 453 431 454 432 }
+24
src/applications/transactions/bulk/type/BulkParameterType.php
··· 1 + <?php 2 + 3 + abstract class BulkParameterType extends Phobject { 4 + 5 + private $viewer; 6 + 7 + final public function setViewer(PhabricatorUser $viewer) { 8 + $this->viewer = $viewer; 9 + return $this; 10 + } 11 + 12 + final public function getViewer() { 13 + return $this->viewer; 14 + } 15 + 16 + abstract public function getPHUIXControlType(); 17 + 18 + public function getPHUIXControlSpecification() { 19 + return array( 20 + 'value' => null, 21 + ); 22 + } 23 + 24 + }
+10
src/applications/transactions/bulk/type/BulkStringParameterType.php
··· 1 + <?php 2 + 3 + final class BulkStringParameterType 4 + extends BulkParameterType { 5 + 6 + public function getPHUIXControlType() { 7 + return 'text'; 8 + } 9 + 10 + }
+65
src/applications/transactions/editengine/PhabricatorEditEngine.php
··· 2421 2421 } 2422 2422 2423 2423 2424 + /* -( Bulk Edits )--------------------------------------------------------- */ 2425 + 2426 + 2427 + final public function newBulkEditMap() { 2428 + $config = $this->loadDefaultConfiguration(); 2429 + if (!$config) { 2430 + throw new Exception( 2431 + pht('No default edit engine configuration for bulk edit.')); 2432 + } 2433 + 2434 + $object = $this->newEditableObject(); 2435 + $fields = $this->buildEditFields($object); 2436 + 2437 + $edit_types = $this->getBulkEditTypesFromFields($fields); 2438 + 2439 + $map = array(); 2440 + foreach ($edit_types as $key => $type) { 2441 + $bulk_type = $type->getBulkParameterType(); 2442 + if ($bulk_type === null) { 2443 + continue; 2444 + } 2445 + 2446 + $bulk_label = $type->getBulkEditLabel(); 2447 + if ($bulk_label === null) { 2448 + continue; 2449 + } 2450 + 2451 + $map[] = array( 2452 + 'label' => $bulk_label, 2453 + 'xaction' => $type->getTransactionType(), 2454 + 'control' => array( 2455 + 'type' => $bulk_type->getPHUIXControlType(), 2456 + 'spec' => (object)$bulk_type->getPHUIXControlSpecification(), 2457 + ), 2458 + ); 2459 + } 2460 + 2461 + return $map; 2462 + } 2463 + 2464 + 2465 + final public function newRawBulkTransactions(array $xactions) { 2466 + return $xactions; 2467 + } 2468 + 2469 + private function getBulkEditTypesFromFields(array $fields) { 2470 + $types = array(); 2471 + 2472 + foreach ($fields as $field) { 2473 + $field_types = $field->getBulkEditTypes(); 2474 + 2475 + if ($field_types === null) { 2476 + continue; 2477 + } 2478 + 2479 + foreach ($field_types as $field_type) { 2480 + $field_type->setField($field); 2481 + $types[$field_type->getEditType()] = $field_type; 2482 + } 2483 + } 2484 + 2485 + return $types; 2486 + } 2487 + 2488 + 2424 2489 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 2425 2490 2426 2491
+60 -1
src/applications/transactions/editfield/PhabricatorEditField.php
··· 17 17 private $previewPanel; 18 18 private $controlID; 19 19 private $controlInstructions; 20 + private $bulkEditLabel; 20 21 21 22 private $description; 22 23 private $conduitDescription; ··· 45 46 private $isConduitOnly = false; 46 47 47 48 private $conduitEditTypes; 49 + private $bulkEditTypes; 48 50 49 51 public function setKey($key) { 50 52 $this->key = $key; ··· 62 64 63 65 public function getLabel() { 64 66 return $this->label; 67 + } 68 + 69 + public function setBulkEditLabel($bulk_edit_label) { 70 + $this->bulkEditLabel = $bulk_edit_label; 71 + return $this; 72 + } 73 + 74 + public function getBulkEditLabel() { 75 + return $this->bulkEditLabel; 65 76 } 66 77 67 78 public function setViewer(PhabricatorUser $viewer) { ··· 625 636 return new AphrontStringHTTPParameterType(); 626 637 } 627 638 639 + protected function getBulkParameterType() { 640 + $type = $this->newBulkParameterType(); 641 + 642 + if (!$type) { 643 + return null; 644 + } 645 + 646 + $type->setViewer($this->getViewer()); 647 + 648 + return $type; 649 + } 650 + 651 + protected function newBulkParameterType() { 652 + return null; 653 + } 654 + 628 655 public function getConduitParameterType() { 629 656 $type = $this->newConduitParameterType(); 630 657 ··· 657 684 return null; 658 685 } 659 686 660 - return id(new PhabricatorSimpleEditType()) 687 + $edit_type = id(new PhabricatorSimpleEditType()) 661 688 ->setConduitParameterType($parameter_type); 689 + 690 + $bulk_type = $this->getBulkParameterType(); 691 + if ($bulk_type) { 692 + $edit_type->setBulkParameterType($bulk_type); 693 + } 694 + 695 + return $edit_type; 662 696 } 663 697 664 698 protected function getEditType() { ··· 709 743 } 710 744 711 745 protected function newConduitEditTypes() { 746 + $edit_type = $this->getEditType(); 747 + 748 + if (!$edit_type) { 749 + return array(); 750 + } 751 + 752 + return array($edit_type); 753 + } 754 + 755 + final public function getBulkEditTypes() { 756 + if ($this->bulkEditTypes === null) { 757 + $edit_types = $this->newBulkEditTypes(); 758 + $edit_types = mpull($edit_types, null, 'getEditType'); 759 + 760 + foreach ($edit_types as $edit_type) { 761 + $edit_type->setEditField($this); 762 + } 763 + 764 + $this->bulkEditTypes = $edit_types; 765 + } 766 + 767 + return $this->bulkEditTypes; 768 + } 769 + 770 + protected function newBulkEditTypes() { 712 771 $edit_type = $this->getEditType(); 713 772 714 773 if (!$edit_type) {
+4
src/applications/transactions/editfield/PhabricatorPHIDListEditField.php
··· 104 104 return $type; 105 105 } 106 106 107 + protected function newBulkEditTypes() { 108 + return $this->newConduitEditTypes(); 109 + } 110 + 107 111 protected function newConduitEditTypes() { 108 112 if (!$this->getUseEdgeTransactions()) { 109 113 return parent::newConduitEditTypes();
+4
src/applications/transactions/editfield/PhabricatorTextEditField.php
··· 29 29 return new ConduitStringParameterType(); 30 30 } 31 31 32 + protected function newBulkParameterType() { 33 + return new BulkStringParameterType(); 34 + } 35 + 32 36 }
+40
src/applications/transactions/edittype/PhabricatorEditType.php
··· 14 14 private $conduitTypeDescription; 15 15 private $conduitParameterType; 16 16 17 + private $bulkParameterType; 18 + private $bulkEditLabel; 19 + 17 20 public function setLabel($label) { 18 21 $this->label = $label; 19 22 return $this; ··· 21 24 22 25 public function getLabel() { 23 26 return $this->label; 27 + } 28 + 29 + public function setBulkEditLabel($bulk_edit_label) { 30 + $this->bulkEditLabel = $bulk_edit_label; 31 + return $this; 32 + } 33 + 34 + public function getBulkEditLabel() { 35 + if ($this->bulkEditLabel !== null) { 36 + return $this->bulkEditLabel; 37 + } 38 + 39 + return $this->getField()->getBulkEditLabel(); 24 40 } 25 41 26 42 public function setField(PhabricatorEditField $field) { ··· 84 100 public function getEditField() { 85 101 return $this->editField; 86 102 } 103 + 104 + 105 + /* -( Bulk )--------------------------------------------------------------- */ 106 + 107 + 108 + protected function newBulkParameterType() { 109 + if ($this->bulkParameterType) { 110 + return clone $this->bulkParameterType; 111 + } 112 + 113 + return null; 114 + } 115 + 116 + 117 + public function setBulkParameterType(BulkParameterType $type) { 118 + $this->bulkParameterType = $type; 119 + return $this; 120 + } 121 + 122 + 123 + public function getBulkParameterType() { 124 + return $this->newBulkParameterType(); 125 + } 126 + 87 127 88 128 /* -( Conduit )------------------------------------------------------------ */ 89 129
-17
webroot/rsrc/css/application/maniphest/batch-editor.css
··· 1 - /** 2 - * @provides maniphest-batch-editor 3 - */ 4 - .maniphest-batch-actions-table { 5 - width: 100%; 6 - margin: 12px 0; 7 - } 8 - 9 - .maniphest-batch-actions-table td { 10 - padding: 4px 8px; 11 - vertical-align: middle; 12 - } 13 - 14 - .batch-editor-input { 15 - width: 100%; 16 - text-align: left; 17 - }
+22
webroot/rsrc/css/phui/phui-bulk-editor.css
··· 1 + /** 2 + * @provides phui-bulk-editor-css 3 + */ 4 + 5 + .bulk-edit-table { 6 + width: 100%; 7 + margin: 12px 0; 8 + } 9 + 10 + .bulk-edit-table td { 11 + padding: 4px 8px; 12 + vertical-align: middle; 13 + } 14 + 15 + .bulk-edit-input { 16 + width: 100%; 17 + text-align: left; 18 + } 19 + 20 + .bulk-edit-input input { 21 + width: 100%; 22 + }
-158
webroot/rsrc/js/application/maniphest/behavior-batch-editor.js
··· 1 - /** 2 - * @provides javelin-behavior-maniphest-batch-editor 3 - * @requires javelin-behavior 4 - * javelin-dom 5 - * javelin-util 6 - * phabricator-prefab 7 - * multirow-row-manager 8 - * javelin-json 9 - */ 10 - 11 - JX.behavior('maniphest-batch-editor', function(config) { 12 - var root = JX.$(config.root); 13 - var editor_table = JX.DOM.find(root, 'table', 'maniphest-batch-actions'); 14 - var manager = new JX.MultirowRowManager(editor_table); 15 - var action_rows = []; 16 - 17 - function renderRow() { 18 - var action_select = JX.Prefab.renderSelect( 19 - { 20 - 'add_project': 'Add Projects', 21 - 'remove_project' : 'Remove Projects', 22 - 'priority': 'Change Priority', 23 - 'status': 'Change Status', 24 - 'add_comment': 'Comment', 25 - 'assign': 'Assign', 26 - 'add_ccs' : 'Add CCs', 27 - 'remove_ccs' : 'Remove CCs', 28 - 'space': 'Shift to Space' 29 - }); 30 - 31 - var proj_tokenizer = build_tokenizer(config.sources.project); 32 - var owner_tokenizer = build_tokenizer(config.sources.owner); 33 - var cc_tokenizer = build_tokenizer(config.sources.cc); 34 - var space_tokenizer = build_tokenizer(config.sources.spaces); 35 - 36 - var priority_select = JX.Prefab.renderSelect(config.priorityMap); 37 - var status_select = JX.Prefab.renderSelect(config.statusMap); 38 - var comment_input = JX.$N('input', {style: {width: '100%'}}); 39 - 40 - var cell = JX.$N('td', {className: 'batch-editor-input'}); 41 - var vfunc = null; 42 - 43 - function update() { 44 - switch (action_select.value) { 45 - case 'add_project': 46 - case 'remove_project': 47 - JX.DOM.setContent(cell, proj_tokenizer.template); 48 - vfunc = function() { 49 - return JX.keys(proj_tokenizer.object.getTokens()); 50 - }; 51 - break; 52 - case 'add_ccs': 53 - case 'remove_ccs': 54 - JX.DOM.setContent(cell, cc_tokenizer.template); 55 - vfunc = function() { 56 - return JX.keys(cc_tokenizer.object.getTokens()); 57 - }; 58 - break; 59 - case 'assign': 60 - JX.DOM.setContent(cell, owner_tokenizer.template); 61 - vfunc = function() { 62 - return JX.keys(owner_tokenizer.object.getTokens()); 63 - }; 64 - break; 65 - case 'space': 66 - JX.DOM.setContent(cell, space_tokenizer.template); 67 - vfunc = function() { 68 - return JX.keys(space_tokenizer.object.getTokens()); 69 - }; 70 - break; 71 - case 'add_comment': 72 - JX.DOM.setContent(cell, comment_input); 73 - vfunc = function() { 74 - return comment_input.value; 75 - }; 76 - break; 77 - case 'priority': 78 - JX.DOM.setContent(cell, priority_select); 79 - vfunc = function() { return priority_select.value; }; 80 - break; 81 - case 'status': 82 - JX.DOM.setContent(cell, status_select); 83 - vfunc = function() { return status_select.value; }; 84 - break; 85 - } 86 - } 87 - 88 - JX.DOM.listen(action_select, 'change', null, update); 89 - update(); 90 - 91 - return { 92 - nodes : [JX.$N('td', {}, action_select), cell], 93 - dataCallback : function() { 94 - return { 95 - action: action_select.value, 96 - value: vfunc() 97 - }; 98 - } 99 - }; 100 - } 101 - 102 - function onaddaction(e) { 103 - e.kill(); 104 - addRow({}); 105 - } 106 - 107 - function addRow(info) { 108 - var data = renderRow(info); 109 - var row = manager.addRow(data.nodes); 110 - var id = manager.getRowID(row); 111 - 112 - action_rows[id] = data.dataCallback; 113 - } 114 - 115 - function onsubmit() { 116 - var input = JX.$(config.input); 117 - 118 - var actions = []; 119 - for (var k in action_rows) { 120 - actions.push(action_rows[k]()); 121 - } 122 - 123 - input.value = JX.JSON.stringify(actions); 124 - } 125 - 126 - addRow({}); 127 - 128 - JX.DOM.listen( 129 - root, 130 - 'click', 131 - 'add-action', 132 - onaddaction); 133 - 134 - JX.DOM.listen( 135 - root, 136 - 'submit', 137 - null, 138 - onsubmit); 139 - 140 - manager.listen( 141 - 'row-removed', 142 - function(row_id) { 143 - delete action_rows[row_id]; 144 - }); 145 - 146 - function build_tokenizer(tconfig) { 147 - var built = JX.Prefab.newTokenizerFromTemplate( 148 - config.tokenizerTemplate, 149 - JX.copy({}, tconfig)); 150 - built.tokenizer.start(); 151 - 152 - return { 153 - object: built.tokenizer, 154 - template: built.node 155 - }; 156 - } 157 - 158 - });
+113
webroot/rsrc/js/core/behavior-bulk-editor.js
··· 1 + /** 2 + * @provides javelin-behavior-bulk-editor 3 + * @requires javelin-behavior 4 + * javelin-dom 5 + * javelin-util 6 + * phabricator-prefab 7 + * multirow-row-manager 8 + * javelin-json 9 + * phuix-form-control-view 10 + */ 11 + 12 + JX.behavior('bulk-editor', function(config) { 13 + 14 + var root = JX.$(config.rootNodeID); 15 + var editor_table = JX.DOM.find(root, 'table', 'bulk-actions'); 16 + 17 + var manager = new JX.MultirowRowManager(editor_table); 18 + var action_rows = []; 19 + 20 + var option_map = {}; 21 + var option_order = []; 22 + var spec_map = {}; 23 + 24 + for (var ii = 0; ii < config.edits.length; ii++) { 25 + var edit = config.edits[ii]; 26 + 27 + option_map[edit.xaction] = edit.label; 28 + option_order.push(edit.xaction); 29 + 30 + spec_map[edit.xaction] = edit; 31 + } 32 + 33 + function renderRow() { 34 + var action_select = JX.Prefab.renderSelect( 35 + option_map, 36 + null, 37 + null, 38 + option_order); 39 + 40 + var cell = JX.$N('td', {className: 'bulk-edit-input'}); 41 + var vfunc = null; 42 + 43 + function update() { 44 + var spec = spec_map[action_select.value]; 45 + var control = spec.control; 46 + 47 + var phuix = new JX.PHUIXFormControl() 48 + .setControl(control.type, control.spec); 49 + 50 + JX.DOM.setContent(cell, phuix.getRawInputNode()); 51 + 52 + vfunc = JX.bind(phuix, phuix.getValue); 53 + } 54 + 55 + JX.DOM.listen(action_select, 'change', null, update); 56 + update(); 57 + 58 + return { 59 + nodes : [JX.$N('td', {}, action_select), cell], 60 + dataCallback : function() { 61 + return { 62 + type: action_select.value, 63 + value: vfunc() 64 + }; 65 + } 66 + }; 67 + } 68 + 69 + function onaddaction(e) { 70 + e.kill(); 71 + addRow({}); 72 + } 73 + 74 + function addRow(info) { 75 + var data = renderRow(info); 76 + var row = manager.addRow(data.nodes); 77 + var id = manager.getRowID(row); 78 + 79 + action_rows[id] = data.dataCallback; 80 + } 81 + 82 + function onsubmit() { 83 + var input = JX.$(config.inputNodeID); 84 + 85 + var actions = []; 86 + for (var k in action_rows) { 87 + actions.push(action_rows[k]()); 88 + } 89 + 90 + input.value = JX.JSON.stringify(actions); 91 + } 92 + 93 + addRow({}); 94 + 95 + JX.DOM.listen( 96 + root, 97 + 'click', 98 + 'add-action', 99 + onaddaction); 100 + 101 + JX.DOM.listen( 102 + root, 103 + 'submit', 104 + null, 105 + onsubmit); 106 + 107 + manager.listen( 108 + 'row-removed', 109 + function(row_id) { 110 + delete action_rows[row_id]; 111 + }); 112 + 113 + });
+13
webroot/rsrc/js/phuix/PHUIXFormControl.js
··· 14 14 _className: null, 15 15 _valueSetCallback: null, 16 16 _valueGetCallback: null, 17 + _rawInputNode: null, 17 18 18 19 setLabel: function(label) { 19 20 JX.DOM.setContent(this._getLabelNode(), label); ··· 53 54 case 'checkboxes': 54 55 input = this._newCheckboxes(spec); 55 56 break; 57 + case 'text': 58 + input = this._newText(spec); 59 + break; 56 60 default: 57 61 // TODO: Default or better error? 58 62 JX.$E('Bad Input Type'); ··· 62 66 JX.DOM.setContent(node, input.node); 63 67 this._valueGetCallback = input.get; 64 68 this._valueSetCallback = input.set; 69 + this._rawInputNode = input.node; 65 70 66 71 return this; 67 72 }, ··· 73 78 74 79 getValue: function() { 75 80 return this._valueGetCallback(); 81 + }, 82 + 83 + getRawInputNode: function() { 84 + return this._rawInputNode; 76 85 }, 77 86 78 87 getNode: function() { ··· 281 290 }, 282 291 283 292 _newPoints: function(spec) { 293 + return this._newText(); 294 + }, 295 + 296 + _newText: function(spec) { 284 297 var attrs = { 285 298 type: 'text', 286 299 value: spec.value