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

Basic stacked action support for EditEngine

Summary: Ref T9132. This still has a lot of rough edges but the basics seem to work OK.

Test Plan: {F1012627}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9132

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

+532 -8
+22
resources/celerity/map.php
··· 420 420 'rsrc/js/application/repository/repository-crossreference.js' => 'e5339c43', 421 421 'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08', 422 422 'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f', 423 + 'rsrc/js/application/transactions/behavior-comment-actions.js' => 'f2c64202', 423 424 'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96', 424 425 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => 'dbbf48b6', 425 426 'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => 'b23b49e6', ··· 498 499 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 499 500 'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262', 500 501 'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca', 502 + 'rsrc/js/phuix/PHUIXFormControl.js' => 'f9fba5ee', 503 + 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', 501 504 ), 502 505 'symbols' => array( 503 506 'almanac-css' => 'dbb9b3af', ··· 561 564 'javelin-behavior-audit-preview' => 'd835b03a', 562 565 'javelin-behavior-bulk-job-reload' => 'edf8a145', 563 566 'javelin-behavior-choose-control' => '6153c708', 567 + 'javelin-behavior-comment-actions' => 'f2c64202', 564 568 'javelin-behavior-config-reorder-fields' => 'b6993408', 565 569 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', 566 570 'javelin-behavior-conpherence-menu' => '1d45c74d', ··· 823 827 'phuix-action-list-view' => 'b5c256b8', 824 828 'phuix-action-view' => '8cf6d262', 825 829 'phuix-dropdown-menu' => 'bd4c8dca', 830 + 'phuix-form-control-view' => 'f9fba5ee', 831 + 'phuix-icon-view' => 'bff6884b', 826 832 'policy-css' => '957ea14c', 827 833 'policy-edit-css' => '815c66f7', 828 834 'policy-transaction-detail-css' => '82100a43', ··· 1767 1773 'javelin-util', 1768 1774 'javelin-request', 1769 1775 ), 1776 + 'bff6884b' => array( 1777 + 'javelin-install', 1778 + 'javelin-dom', 1779 + ), 1770 1780 'c1700f6f' => array( 1771 1781 'javelin-install', 1772 1782 'javelin-util', ··· 1973 1983 'javelin-workflow', 1974 1984 'javelin-json', 1975 1985 ), 1986 + 'f2c64202' => array( 1987 + 'javelin-behavior', 1988 + 'javelin-stratcom', 1989 + 'javelin-workflow', 1990 + 'javelin-dom', 1991 + 'phuix-form-control-view', 1992 + 'phuix-icon-view', 1993 + ), 1976 1994 'f36e01af' => array( 1977 1995 'javelin-behavior', 1978 1996 'javelin-behavior-device', ··· 2028 2046 'javelin-mask', 2029 2047 'javelin-util', 2030 2048 'phabricator-busy', 2049 + ), 2050 + 'f9fba5ee' => array( 2051 + 'javelin-install', 2052 + 'javelin-dom', 2031 2053 ), 2032 2054 'fa0f4fc2' => array( 2033 2055 'javelin-behavior',
+1
src/applications/paste/controller/PhabricatorPasteViewController.php
··· 34 34 ->setViewer($viewer) 35 35 ->withIDs(array($id)) 36 36 ->needContent(true) 37 + ->needRawContent(true) 37 38 ->executeOne(); 38 39 if (!$paste) { 39 40 return new Aphront404Response();
+1
src/applications/project/editor/PhabricatorProjectsEditEngineExtension.php
··· 53 53 pht('Add projects.'), 54 54 pht('Remove projects.'), 55 55 pht('Set associated projects, overwriting current value.')) 56 + ->setCommentActionLabel(pht('Add Projects')) 56 57 ->setTransactionType($edge_type) 57 58 ->setMetadataValue('edge:type', $project_edge_type) 58 59 ->setValue($project_phids);
+1
src/applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php
··· 50 50 pht('Add subscribers.'), 51 51 pht('Remove subscribers.'), 52 52 pht('Set subscribers, overwriting current value.')) 53 + ->setCommentActionLabel(pht('Add Subscribers')) 53 54 ->setTransactionType($subscribers_type) 54 55 ->setValue($sub_phids); 55 56
+58 -5
src/applications/transactions/editengine/PhabricatorEditEngine.php
··· 873 873 } 874 874 875 875 final public function buildEditEngineCommentView($object) { 876 + $config = $this->loadEditEngineConfiguration(null); 877 + 876 878 $viewer = $this->getViewer(); 877 879 $object_phid = $object->getPHID(); 878 880 ··· 896 898 } 897 899 898 900 $view->setCurrentVersion($this->loadDraftVersion($object)); 901 + 902 + $fields = $this->buildEditFields($object); 903 + 904 + $all_types = array(); 905 + foreach ($fields as $field) { 906 + // TODO: Load draft stuff. 907 + $types = $field->getCommentEditTypes(); 908 + foreach ($types as $type) { 909 + $all_types[] = $type; 910 + } 911 + } 912 + 913 + $view->setEditTypes($all_types); 899 914 900 915 return $view; 901 916 } ··· 999 1014 return new Aphront400Response(); 1000 1015 } 1001 1016 1017 + $config = $this->loadEditEngineConfiguration(null); 1018 + $fields = $this->buildEditFields($object); 1019 + 1002 1020 $is_preview = $request->isPreviewRequest(); 1003 1021 $view_uri = $this->getObjectViewURI($object); 1004 1022 ··· 1025 1043 1026 1044 $xactions = array(); 1027 1045 1028 - $xactions[] = id(clone $template) 1029 - ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) 1030 - ->attachComment( 1031 - id(clone $comment_template) 1032 - ->setContent($comment_text)); 1046 + $actions = $request->getStr('editengine.actions'); 1047 + if ($actions) { 1048 + $type_map = array(); 1049 + foreach ($fields as $field) { 1050 + $types = $field->getCommentEditTypes(); 1051 + foreach ($types as $type) { 1052 + $type_map[$type->getEditType()] = $type; 1053 + } 1054 + } 1055 + 1056 + $actions = phutil_json_decode($actions); 1057 + foreach ($actions as $action) { 1058 + $type = idx($action, 'type'); 1059 + if (!$type) { 1060 + continue; 1061 + } 1062 + 1063 + $edit_type = idx($type_map, $type); 1064 + if (!$edit_type) { 1065 + continue; 1066 + } 1067 + 1068 + $type_xactions = $edit_type->generateTransactions( 1069 + $template, 1070 + array( 1071 + 'value' => idx($action, 'value'), 1072 + )); 1073 + foreach ($type_xactions as $type_xaction) { 1074 + $xactions[] = $type_xaction; 1075 + } 1076 + } 1077 + } 1078 + 1079 + if (strlen($comment_text) || !$xactions) { 1080 + $xactions[] = id(clone $template) 1081 + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) 1082 + ->attachComment( 1083 + id(clone $comment_template) 1084 + ->setContent($comment_text)); 1085 + } 1033 1086 1034 1087 $editor = $object->getApplicationTransactionEditor() 1035 1088 ->setActor($viewer)
+4
src/applications/transactions/editfield/PhabricatorEditField.php
··· 494 494 return array($edit_type); 495 495 } 496 496 497 + public function getCommentEditTypes() { 498 + return array(); 499 + } 500 + 497 501 }
+49
src/applications/transactions/editfield/PhabricatorTokenizerEditField.php
··· 3 3 abstract class PhabricatorTokenizerEditField 4 4 extends PhabricatorPHIDListEditField { 5 5 6 + private $commentActionLabel; 7 + 6 8 abstract protected function newDatasource(); 9 + 10 + public function setCommentActionLabel($label) { 11 + $this->commentActionLabel = $label; 12 + return $this; 13 + } 14 + 15 + public function getCommentActionLabel() { 16 + return $this->commentActionLabel; 17 + } 7 18 8 19 protected function newControl() { 9 20 $control = id(new AphrontFormTokenizerControl()) ··· 19 30 20 31 protected function getInitialValueFromSubmit(AphrontRequest $request, $key) { 21 32 return $request->getArr($key.'.original'); 33 + } 34 + 35 + protected function newEditType() { 36 + $type = parent::newEditType(); 37 + 38 + if ($this->getUseEdgeTransactions()) { 39 + $datasource = $this->newDatasource() 40 + ->setViewer($this->getViewer()); 41 + $type->setDatasource($datasource); 42 + } 43 + 44 + return $type; 45 + } 46 + 47 + public function getCommentEditTypes() { 48 + if (!$this->getUseEdgeTransactions()) { 49 + return parent::getCommentEditTypes(); 50 + } 51 + 52 + $transaction_type = $this->getTransactionType(); 53 + if ($transaction_type === null) { 54 + return array(); 55 + } 56 + 57 + $label = $this->getCommentActionLabel(); 58 + if ($label === null) { 59 + return array(); 60 + } 61 + 62 + $type_key = $this->getEditTypeKey(); 63 + $base = $this->getEditType(); 64 + 65 + $add = id(clone $base) 66 + ->setEditType($type_key.'.add') 67 + ->setEdgeOperation('+') 68 + ->setLabel($label); 69 + 70 + return array($add); 22 71 } 23 72 24 73 }
+11 -1
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 811 811 $this->adjustTransactionValues($object, $xaction); 812 812 } 813 813 814 - $xactions = $this->filterTransactions($object, $xactions); 814 + try { 815 + $xactions = $this->filterTransactions($object, $xactions); 816 + } catch (Exception $ex) { 817 + if ($read_locking) { 818 + $object->endReadLocking(); 819 + } 820 + if ($transaction_open) { 821 + $object->killTransaction(); 822 + } 823 + throw $ex; 824 + } 815 825 816 826 // Now that we've merged, filtered, and combined transactions, check for 817 827 // required capabilities.
+39
src/applications/transactions/edittype/PhabricatorEdgeEditType.php
··· 4 4 5 5 private $edgeOperation; 6 6 private $valueDescription; 7 + private $datasource; 7 8 8 9 public function setEdgeOperation($edge_operation) { 9 10 $this->edgeOperation = $edge_operation; ··· 12 13 13 14 public function getEdgeOperation() { 14 15 return $this->edgeOperation; 16 + } 17 + 18 + public function setDatasource($datasource) { 19 + $this->datasource = $datasource; 20 + return $this; 21 + } 22 + 23 + public function getDatasource() { 24 + return $this->datasource; 15 25 } 16 26 17 27 public function getValueType() { ··· 44 54 45 55 public function getValueDescription() { 46 56 return $this->valueDescription; 57 + } 58 + 59 + public function getPHUIXControlType() { 60 + $datasource = $this->getDatasource(); 61 + 62 + if (!$datasource) { 63 + return null; 64 + } 65 + 66 + return 'tokenizer'; 67 + } 68 + 69 + public function getPHUIXControlSpecification() { 70 + $datasource = $this->getDatasource(); 71 + 72 + if (!$datasource) { 73 + return null; 74 + } 75 + 76 + $template = new AphrontTokenizerTemplateView(); 77 + 78 + return array( 79 + 'markup' => $template->render(), 80 + 'config' => array( 81 + 'src' => $datasource->getDatasourceURI(), 82 + 'browseURI' => $datasource->getBrowseURI(), 83 + 'placeholder' => $datasource->getPlaceholderText(), 84 + ), 85 + ); 47 86 } 48 87 49 88 }
+18
src/applications/transactions/edittype/PhabricatorEditType.php
··· 4 4 5 5 private $editType; 6 6 private $transactionType; 7 + private $label; 7 8 private $field; 8 9 private $description; 9 10 private $summary; ··· 28 29 return $this->getDescription(); 29 30 } 30 31 return $this->summary; 32 + } 33 + 34 + public function setLabel($label) { 35 + $this->label = $label; 36 + return $this; 37 + } 38 + 39 + public function getLabel() { 40 + return $this->label; 31 41 } 32 42 33 43 public function setField(PhabricatorEditField $field) { ··· 84 94 } 85 95 86 96 return $xaction; 97 + } 98 + 99 + public function getPHUIXControlType() { 100 + return null; 101 + } 102 + 103 + public function getPHUIXControlSpecification() { 104 + return null; 87 105 } 88 106 89 107 }
+64 -2
src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
··· 22 22 23 23 private $currentVersion; 24 24 private $versionedDraft; 25 + private $editTypes; 25 26 26 27 public function setObjectPHID($object_phid) { 27 28 $this->objectPHID = $object_phid; ··· 100 101 return $this; 101 102 } 102 103 104 + public function setEditTypes($edit_types) { 105 + $this->editTypes = $edit_types; 106 + return $this; 107 + } 108 + 109 + public function getEditTypes() { 110 + return $this->editTypes; 111 + } 112 + 103 113 public function render() { 104 114 105 115 $user = $this->getUser(); ··· 182 192 $version_key = PhabricatorVersionedDraft::KEY_VERSION; 183 193 $version_value = $this->getCurrentVersion(); 184 194 185 - return id(new AphrontFormView()) 195 + $form = id(new AphrontFormView()) 186 196 ->setUser($this->getUser()) 187 197 ->addSigil('transaction-append') 188 198 ->setWorkflow(true) ··· 193 203 ->setAction($this->getAction()) 194 204 ->setID($this->getFormID()) 195 205 ->addHiddenInput('__draft__', $draft_key) 196 - ->addHiddenInput($version_key, $version_value) 206 + ->addHiddenInput($version_key, $version_value); 207 + 208 + $edit_types = $this->getEditTypes(); 209 + if ($edit_types) { 210 + 211 + $action_map = array(); 212 + foreach ($edit_types as $edit_type) { 213 + $key = $edit_type->getEditType(); 214 + $action_map[$key] = array( 215 + 'key' => $key, 216 + 'label' => $edit_type->getLabel(), 217 + 'type' => $edit_type->getPHUIXControlType(), 218 + 'spec' => $edit_type->getPHUIXControlSpecification(), 219 + ); 220 + } 221 + 222 + $options = array(); 223 + $options['+'] = pht('Add Action...'); 224 + foreach ($action_map as $key => $item) { 225 + $options[$key] = $item['label']; 226 + } 227 + 228 + $action_id = celerity_generate_unique_node_id(); 229 + $input_id = celerity_generate_unique_node_id(); 230 + 231 + $form->appendChild( 232 + phutil_tag( 233 + 'input', 234 + array( 235 + 'type' => 'hidden', 236 + 'name' => 'editengine.actions', 237 + 'id' => $input_id, 238 + ))); 239 + 240 + $form->appendChild( 241 + id(new AphrontFormSelectControl()) 242 + ->setLabel(pht('Actions')) 243 + ->setID($action_id) 244 + ->setOptions($options)); 245 + 246 + Javelin::initBehavior( 247 + 'comment-actions', 248 + array( 249 + 'actionID' => $action_id, 250 + 'inputID' => $input_id, 251 + 'formID' => $this->getFormID(), 252 + 'actions' => $action_map, 253 + )); 254 + } 255 + 256 + $form 197 257 ->appendChild( 198 258 id(new PhabricatorRemarkupControl()) 199 259 ->setID($this->getCommentID()) ··· 207 267 ->appendChild( 208 268 id(new AphrontFormMarkupControl()) 209 269 ->setValue($status)); 270 + 271 + return $form; 210 272 } 211 273 212 274 private function renderPreviewPanel() {
+84
webroot/rsrc/js/application/transactions/behavior-comment-actions.js
··· 1 + /** 2 + * @provides javelin-behavior-comment-actions 3 + * @requires javelin-behavior 4 + * javelin-stratcom 5 + * javelin-workflow 6 + * javelin-dom 7 + * phuix-form-control-view 8 + * phuix-icon-view 9 + */ 10 + 11 + JX.behavior('comment-actions', function(config) { 12 + var action_map = config.actions; 13 + 14 + var action_node = JX.$(config.actionID); 15 + var form_node = JX.$(config.formID); 16 + var input_node = JX.$(config.inputID); 17 + 18 + var rows = {}; 19 + 20 + JX.DOM.listen(action_node, 'change', null, function() { 21 + var options = action_node.options; 22 + var option; 23 + 24 + var selected = action_node.value; 25 + action_node.value = '+'; 26 + 27 + for (var ii = 0; ii < options.length; ii++) { 28 + option = options[ii]; 29 + if (option.value == selected) { 30 + add_row(option); 31 + break; 32 + } 33 + } 34 + }); 35 + 36 + JX.DOM.listen(form_node, 'submit', null, function() { 37 + var data = []; 38 + 39 + for (var k in rows) { 40 + data.push({ 41 + type: k, 42 + value: rows[k].getValue() 43 + }); 44 + } 45 + 46 + input_node.value = JX.JSON.stringify(data); 47 + }); 48 + 49 + function add_row(option) { 50 + var action = action_map[option.value]; 51 + if (!action) { 52 + return; 53 + } 54 + 55 + option.disabled = true; 56 + 57 + var icon = new JX.PHUIXIconView() 58 + .setIcon('fa-times-circle'); 59 + var remove = JX.$N('a', {href: '#'}, icon.getNode()); 60 + 61 + var control = new JX.PHUIXFormControl() 62 + .setLabel(action.label) 63 + .setError(remove) 64 + .setControl('tokenizer', action.spec); 65 + var node = control.getNode(); 66 + 67 + rows[action.key] = control; 68 + 69 + JX.DOM.listen(remove, 'click', null, function(e) { 70 + e.kill(); 71 + JX.DOM.remove(node); 72 + delete rows[action.key]; 73 + option.disabled = false; 74 + }); 75 + 76 + // TODO: Grotesque. 77 + action_node 78 + .parentNode 79 + .parentNode 80 + .parentNode 81 + .insertBefore(node, action_node.parentNode.parentNode.nextSibling); 82 + } 83 + 84 + });
+133
webroot/rsrc/js/phuix/PHUIXFormControl.js
··· 1 + /** 2 + * @provides phuix-form-control-view 3 + * @requires javelin-install 4 + * javelin-dom 5 + */ 6 + 7 + JX.install('PHUIXFormControl', { 8 + 9 + members: { 10 + _node: null, 11 + _labelNode: null, 12 + _errorNode: null, 13 + _inputNode: null, 14 + _valueSetCallback: null, 15 + _valueGetCallback: null, 16 + 17 + setLabel: function(label) { 18 + JX.DOM.setContent(this._getLabelNode(), label); 19 + return this; 20 + }, 21 + 22 + setError: function(error) { 23 + JX.DOM.setContent(this._getErrorNode(), error); 24 + return this; 25 + }, 26 + 27 + setControl: function(type, spec) { 28 + var node = this._getInputNode(); 29 + 30 + var input; 31 + switch (type) { 32 + case 'tokenizer': 33 + input = this._newTokenizer(spec); 34 + break; 35 + default: 36 + // TODO: Default or better error? 37 + JX.$E('Bad Input Type'); 38 + return; 39 + } 40 + 41 + JX.DOM.setContent(node, input.node); 42 + this._valueGetCallback = input.get; 43 + this._valueSetCallback = input.set; 44 + 45 + return this; 46 + }, 47 + 48 + setValue: function(value) { 49 + this._valueSetCallback(value); 50 + return this; 51 + }, 52 + 53 + getValue: function() { 54 + return this._valueGetCallback(); 55 + }, 56 + 57 + getNode: function() { 58 + if (!this._node) { 59 + 60 + var attrs = { 61 + className: 'aphront-form-control grouped' 62 + }; 63 + 64 + var content = [ 65 + this._getLabelNode(), 66 + this._getErrorNode(), 67 + this._getInputNode() 68 + ]; 69 + 70 + this._node = JX.$N('div', attrs, content); 71 + } 72 + 73 + return this._node; 74 + }, 75 + 76 + _getLabelNode: function() { 77 + if (!this._labelNode) { 78 + var attrs = { 79 + className: 'aphront-form-label' 80 + }; 81 + 82 + this._labelNode = JX.$N('label', attrs); 83 + } 84 + 85 + return this._labelNode; 86 + }, 87 + 88 + _getErrorNode: function() { 89 + if (!this._errorNode) { 90 + var attrs = { 91 + className: 'aphront-form-error' 92 + }; 93 + 94 + this._errorNode = JX.$N('span', attrs); 95 + } 96 + 97 + return this._errorNode; 98 + }, 99 + 100 + _getInputNode: function() { 101 + if (!this._inputNode) { 102 + var attrs = { 103 + className: 'aphront-form-input' 104 + }; 105 + 106 + this._inputNode = JX.$N('div', attrs); 107 + } 108 + 109 + return this._inputNode; 110 + }, 111 + 112 + _newTokenizer: function(spec) { 113 + var build = JX.Prefab.newTokenizerFromTemplate( 114 + spec.markup, 115 + spec.config); 116 + build.tokenizer.start(); 117 + 118 + return { 119 + node: build.node, 120 + get: function() { 121 + return JX.keys(build.tokenizer.getTokens()); 122 + }, 123 + set: function(map) { 124 + for (var k in map) { 125 + build.tokenizer.addToken(k, map[k]); 126 + } 127 + } 128 + }; 129 + } 130 + 131 + } 132 + 133 + });
+47
webroot/rsrc/js/phuix/PHUIXIconView.js
··· 1 + /** 2 + * @provides phuix-icon-view 3 + * @requires javelin-install 4 + * javelin-dom 5 + */ 6 + 7 + JX.install('PHUIXIconView', { 8 + 9 + members: { 10 + _node: null, 11 + _icon: null, 12 + _color: null, 13 + 14 + setIcon: function(icon) { 15 + var node = this.getNode(); 16 + if (this._icon) { 17 + JX.DOM.alterClass(node, this._icon, false); 18 + } 19 + this._icon = icon; 20 + JX.DOM.alterClass(node, this._icon, true); 21 + return this; 22 + }, 23 + 24 + setColor: function(color) { 25 + var node = this.getNode(); 26 + if (this._color) { 27 + JX.DOM.alterClass(node, this._color, false); 28 + } 29 + this._color = color; 30 + JX.DOM.alterClass(node, this._color, true); 31 + return this; 32 + }, 33 + 34 + getNode: function() { 35 + if (!this._node) { 36 + var attrs = { 37 + className: 'phui-icon-view phui-font-fa' 38 + }; 39 + 40 + this._node = JX.$N('span', attrs); 41 + } 42 + 43 + return this._node; 44 + } 45 + } 46 + 47 + });