Page MenuHomePhabricator

D14653.diff
No OneTemporary

D14653.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -420,6 +420,7 @@
'rsrc/js/application/repository/repository-crossreference.js' => 'e5339c43',
'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08',
'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f',
+ 'rsrc/js/application/transactions/behavior-comment-actions.js' => 'f2c64202',
'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96',
'rsrc/js/application/transactions/behavior-show-older-transactions.js' => 'dbbf48b6',
'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => 'b23b49e6',
@@ -498,6 +499,8 @@
'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8',
'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca',
+ 'rsrc/js/phuix/PHUIXFormControl.js' => 'f9fba5ee',
+ 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
),
'symbols' => array(
'almanac-css' => 'dbb9b3af',
@@ -561,6 +564,7 @@
'javelin-behavior-audit-preview' => 'd835b03a',
'javelin-behavior-bulk-job-reload' => 'edf8a145',
'javelin-behavior-choose-control' => '6153c708',
+ 'javelin-behavior-comment-actions' => 'f2c64202',
'javelin-behavior-config-reorder-fields' => 'b6993408',
'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a',
'javelin-behavior-conpherence-menu' => '1d45c74d',
@@ -823,6 +827,8 @@
'phuix-action-list-view' => 'b5c256b8',
'phuix-action-view' => '8cf6d262',
'phuix-dropdown-menu' => 'bd4c8dca',
+ 'phuix-form-control-view' => 'f9fba5ee',
+ 'phuix-icon-view' => 'bff6884b',
'policy-css' => '957ea14c',
'policy-edit-css' => '815c66f7',
'policy-transaction-detail-css' => '82100a43',
@@ -1767,6 +1773,10 @@
'javelin-util',
'javelin-request',
),
+ 'bff6884b' => array(
+ 'javelin-install',
+ 'javelin-dom',
+ ),
'c1700f6f' => array(
'javelin-install',
'javelin-util',
@@ -1973,6 +1983,14 @@
'javelin-workflow',
'javelin-json',
),
+ 'f2c64202' => array(
+ 'javelin-behavior',
+ 'javelin-stratcom',
+ 'javelin-workflow',
+ 'javelin-dom',
+ 'phuix-form-control-view',
+ 'phuix-icon-view',
+ ),
'f36e01af' => array(
'javelin-behavior',
'javelin-behavior-device',
@@ -2029,6 +2047,10 @@
'javelin-util',
'phabricator-busy',
),
+ 'f9fba5ee' => array(
+ 'javelin-install',
+ 'javelin-dom',
+ ),
'fa0f4fc2' => array(
'javelin-behavior',
'javelin-dom',
diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php
--- a/src/applications/paste/controller/PhabricatorPasteViewController.php
+++ b/src/applications/paste/controller/PhabricatorPasteViewController.php
@@ -34,6 +34,7 @@
->setViewer($viewer)
->withIDs(array($id))
->needContent(true)
+ ->needRawContent(true)
->executeOne();
if (!$paste) {
return new Aphront404Response();
diff --git a/src/applications/project/editor/PhabricatorProjectsEditEngineExtension.php b/src/applications/project/editor/PhabricatorProjectsEditEngineExtension.php
--- a/src/applications/project/editor/PhabricatorProjectsEditEngineExtension.php
+++ b/src/applications/project/editor/PhabricatorProjectsEditEngineExtension.php
@@ -53,6 +53,7 @@
pht('Add projects.'),
pht('Remove projects.'),
pht('Set associated projects, overwriting current value.'))
+ ->setCommentActionLabel(pht('Add Projects'))
->setTransactionType($edge_type)
->setMetadataValue('edge:type', $project_edge_type)
->setValue($project_phids);
diff --git a/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php b/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php
--- a/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php
+++ b/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php
@@ -50,6 +50,7 @@
pht('Add subscribers.'),
pht('Remove subscribers.'),
pht('Set subscribers, overwriting current value.'))
+ ->setCommentActionLabel(pht('Add Subscribers'))
->setTransactionType($subscribers_type)
->setValue($sub_phids);
diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php
--- a/src/applications/transactions/editengine/PhabricatorEditEngine.php
+++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php
@@ -873,6 +873,8 @@
}
final public function buildEditEngineCommentView($object) {
+ $config = $this->loadEditEngineConfiguration(null);
+
$viewer = $this->getViewer();
$object_phid = $object->getPHID();
@@ -897,6 +899,19 @@
$view->setCurrentVersion($this->loadDraftVersion($object));
+ $fields = $this->buildEditFields($object);
+
+ $all_types = array();
+ foreach ($fields as $field) {
+ // TODO: Load draft stuff.
+ $types = $field->getCommentEditTypes();
+ foreach ($types as $type) {
+ $all_types[] = $type;
+ }
+ }
+
+ $view->setEditTypes($all_types);
+
return $view;
}
@@ -999,6 +1014,9 @@
return new Aphront400Response();
}
+ $config = $this->loadEditEngineConfiguration(null);
+ $fields = $this->buildEditFields($object);
+
$is_preview = $request->isPreviewRequest();
$view_uri = $this->getObjectViewURI($object);
@@ -1025,11 +1043,46 @@
$xactions = array();
- $xactions[] = id(clone $template)
- ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
- ->attachComment(
- id(clone $comment_template)
- ->setContent($comment_text));
+ $actions = $request->getStr('editengine.actions');
+ if ($actions) {
+ $type_map = array();
+ foreach ($fields as $field) {
+ $types = $field->getCommentEditTypes();
+ foreach ($types as $type) {
+ $type_map[$type->getEditType()] = $type;
+ }
+ }
+
+ $actions = phutil_json_decode($actions);
+ foreach ($actions as $action) {
+ $type = idx($action, 'type');
+ if (!$type) {
+ continue;
+ }
+
+ $edit_type = idx($type_map, $type);
+ if (!$edit_type) {
+ continue;
+ }
+
+ $type_xactions = $edit_type->generateTransactions(
+ $template,
+ array(
+ 'value' => idx($action, 'value'),
+ ));
+ foreach ($type_xactions as $type_xaction) {
+ $xactions[] = $type_xaction;
+ }
+ }
+ }
+
+ if (strlen($comment_text) || !$xactions) {
+ $xactions[] = id(clone $template)
+ ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
+ ->attachComment(
+ id(clone $comment_template)
+ ->setContent($comment_text));
+ }
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer)
diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php
--- a/src/applications/transactions/editfield/PhabricatorEditField.php
+++ b/src/applications/transactions/editfield/PhabricatorEditField.php
@@ -494,4 +494,8 @@
return array($edit_type);
}
+ public function getCommentEditTypes() {
+ return array();
+ }
+
}
diff --git a/src/applications/transactions/editfield/PhabricatorTokenizerEditField.php b/src/applications/transactions/editfield/PhabricatorTokenizerEditField.php
--- a/src/applications/transactions/editfield/PhabricatorTokenizerEditField.php
+++ b/src/applications/transactions/editfield/PhabricatorTokenizerEditField.php
@@ -3,8 +3,19 @@
abstract class PhabricatorTokenizerEditField
extends PhabricatorPHIDListEditField {
+ private $commentActionLabel;
+
abstract protected function newDatasource();
+ public function setCommentActionLabel($label) {
+ $this->commentActionLabel = $label;
+ return $this;
+ }
+
+ public function getCommentActionLabel() {
+ return $this->commentActionLabel;
+ }
+
protected function newControl() {
$control = id(new AphrontFormTokenizerControl())
->setDatasource($this->newDatasource());
@@ -21,4 +32,42 @@
return $request->getArr($key.'.original');
}
+ protected function newEditType() {
+ $type = parent::newEditType();
+
+ if ($this->getUseEdgeTransactions()) {
+ $datasource = $this->newDatasource()
+ ->setViewer($this->getViewer());
+ $type->setDatasource($datasource);
+ }
+
+ return $type;
+ }
+
+ public function getCommentEditTypes() {
+ if (!$this->getUseEdgeTransactions()) {
+ return parent::getCommentEditTypes();
+ }
+
+ $transaction_type = $this->getTransactionType();
+ if ($transaction_type === null) {
+ return array();
+ }
+
+ $label = $this->getCommentActionLabel();
+ if ($label === null) {
+ return array();
+ }
+
+ $type_key = $this->getEditTypeKey();
+ $base = $this->getEditType();
+
+ $add = id(clone $base)
+ ->setEditType($type_key.'.add')
+ ->setEdgeOperation('+')
+ ->setLabel($label);
+
+ return array($add);
+ }
+
}
diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
--- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
+++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
@@ -811,7 +811,17 @@
$this->adjustTransactionValues($object, $xaction);
}
- $xactions = $this->filterTransactions($object, $xactions);
+ try {
+ $xactions = $this->filterTransactions($object, $xactions);
+ } catch (Exception $ex) {
+ if ($read_locking) {
+ $object->endReadLocking();
+ }
+ if ($transaction_open) {
+ $object->killTransaction();
+ }
+ throw $ex;
+ }
// Now that we've merged, filtered, and combined transactions, check for
// required capabilities.
diff --git a/src/applications/transactions/edittype/PhabricatorEdgeEditType.php b/src/applications/transactions/edittype/PhabricatorEdgeEditType.php
--- a/src/applications/transactions/edittype/PhabricatorEdgeEditType.php
+++ b/src/applications/transactions/edittype/PhabricatorEdgeEditType.php
@@ -4,6 +4,7 @@
private $edgeOperation;
private $valueDescription;
+ private $datasource;
public function setEdgeOperation($edge_operation) {
$this->edgeOperation = $edge_operation;
@@ -14,6 +15,15 @@
return $this->edgeOperation;
}
+ public function setDatasource($datasource) {
+ $this->datasource = $datasource;
+ return $this;
+ }
+
+ public function getDatasource() {
+ return $this->datasource;
+ }
+
public function getValueType() {
return 'list<phid>';
}
@@ -46,4 +56,33 @@
return $this->valueDescription;
}
+ public function getPHUIXControlType() {
+ $datasource = $this->getDatasource();
+
+ if (!$datasource) {
+ return null;
+ }
+
+ return 'tokenizer';
+ }
+
+ public function getPHUIXControlSpecification() {
+ $datasource = $this->getDatasource();
+
+ if (!$datasource) {
+ return null;
+ }
+
+ $template = new AphrontTokenizerTemplateView();
+
+ return array(
+ 'markup' => $template->render(),
+ 'config' => array(
+ 'src' => $datasource->getDatasourceURI(),
+ 'browseURI' => $datasource->getBrowseURI(),
+ 'placeholder' => $datasource->getPlaceholderText(),
+ ),
+ );
+ }
+
}
diff --git a/src/applications/transactions/edittype/PhabricatorEditType.php b/src/applications/transactions/edittype/PhabricatorEditType.php
--- a/src/applications/transactions/edittype/PhabricatorEditType.php
+++ b/src/applications/transactions/edittype/PhabricatorEditType.php
@@ -4,6 +4,7 @@
private $editType;
private $transactionType;
+ private $label;
private $field;
private $description;
private $summary;
@@ -30,6 +31,15 @@
return $this->summary;
}
+ public function setLabel($label) {
+ $this->label = $label;
+ return $this;
+ }
+
+ public function getLabel() {
+ return $this->label;
+ }
+
public function setField(PhabricatorEditField $field) {
$this->field = $field;
return $this;
@@ -86,4 +96,12 @@
return $xaction;
}
+ public function getPHUIXControlType() {
+ return null;
+ }
+
+ public function getPHUIXControlSpecification() {
+ return null;
+ }
+
}
diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
--- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
+++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
@@ -22,6 +22,7 @@
private $currentVersion;
private $versionedDraft;
+ private $editTypes;
public function setObjectPHID($object_phid) {
$this->objectPHID = $object_phid;
@@ -100,6 +101,15 @@
return $this;
}
+ public function setEditTypes($edit_types) {
+ $this->editTypes = $edit_types;
+ return $this;
+ }
+
+ public function getEditTypes() {
+ return $this->editTypes;
+ }
+
public function render() {
$user = $this->getUser();
@@ -182,7 +192,7 @@
$version_key = PhabricatorVersionedDraft::KEY_VERSION;
$version_value = $this->getCurrentVersion();
- return id(new AphrontFormView())
+ $form = id(new AphrontFormView())
->setUser($this->getUser())
->addSigil('transaction-append')
->setWorkflow(true)
@@ -193,7 +203,57 @@
->setAction($this->getAction())
->setID($this->getFormID())
->addHiddenInput('__draft__', $draft_key)
- ->addHiddenInput($version_key, $version_value)
+ ->addHiddenInput($version_key, $version_value);
+
+ $edit_types = $this->getEditTypes();
+ if ($edit_types) {
+
+ $action_map = array();
+ foreach ($edit_types as $edit_type) {
+ $key = $edit_type->getEditType();
+ $action_map[$key] = array(
+ 'key' => $key,
+ 'label' => $edit_type->getLabel(),
+ 'type' => $edit_type->getPHUIXControlType(),
+ 'spec' => $edit_type->getPHUIXControlSpecification(),
+ );
+ }
+
+ $options = array();
+ $options['+'] = pht('Add Action...');
+ foreach ($action_map as $key => $item) {
+ $options[$key] = $item['label'];
+ }
+
+ $action_id = celerity_generate_unique_node_id();
+ $input_id = celerity_generate_unique_node_id();
+
+ $form->appendChild(
+ phutil_tag(
+ 'input',
+ array(
+ 'type' => 'hidden',
+ 'name' => 'editengine.actions',
+ 'id' => $input_id,
+ )));
+
+ $form->appendChild(
+ id(new AphrontFormSelectControl())
+ ->setLabel(pht('Actions'))
+ ->setID($action_id)
+ ->setOptions($options));
+
+ Javelin::initBehavior(
+ 'comment-actions',
+ array(
+ 'actionID' => $action_id,
+ 'inputID' => $input_id,
+ 'formID' => $this->getFormID(),
+ 'actions' => $action_map,
+ ));
+ }
+
+ $form
->appendChild(
id(new PhabricatorRemarkupControl())
->setID($this->getCommentID())
@@ -207,6 +267,8 @@
->appendChild(
id(new AphrontFormMarkupControl())
->setValue($status));
+
+ return $form;
}
private function renderPreviewPanel() {
diff --git a/webroot/rsrc/js/application/transactions/behavior-comment-actions.js b/webroot/rsrc/js/application/transactions/behavior-comment-actions.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/application/transactions/behavior-comment-actions.js
@@ -0,0 +1,84 @@
+/**
+ * @provides javelin-behavior-comment-actions
+ * @requires javelin-behavior
+ * javelin-stratcom
+ * javelin-workflow
+ * javelin-dom
+ * phuix-form-control-view
+ * phuix-icon-view
+ */
+
+JX.behavior('comment-actions', function(config) {
+ var action_map = config.actions;
+
+ var action_node = JX.$(config.actionID);
+ var form_node = JX.$(config.formID);
+ var input_node = JX.$(config.inputID);
+
+ var rows = {};
+
+ JX.DOM.listen(action_node, 'change', null, function() {
+ var options = action_node.options;
+ var option;
+
+ var selected = action_node.value;
+ action_node.value = '+';
+
+ for (var ii = 0; ii < options.length; ii++) {
+ option = options[ii];
+ if (option.value == selected) {
+ add_row(option);
+ break;
+ }
+ }
+ });
+
+ JX.DOM.listen(form_node, 'submit', null, function() {
+ var data = [];
+
+ for (var k in rows) {
+ data.push({
+ type: k,
+ value: rows[k].getValue()
+ });
+ }
+
+ input_node.value = JX.JSON.stringify(data);
+ });
+
+ function add_row(option) {
+ var action = action_map[option.value];
+ if (!action) {
+ return;
+ }
+
+ option.disabled = true;
+
+ var icon = new JX.PHUIXIconView()
+ .setIcon('fa-times-circle');
+ var remove = JX.$N('a', {href: '#'}, icon.getNode());
+
+ var control = new JX.PHUIXFormControl()
+ .setLabel(action.label)
+ .setError(remove)
+ .setControl('tokenizer', action.spec);
+ var node = control.getNode();
+
+ rows[action.key] = control;
+
+ JX.DOM.listen(remove, 'click', null, function(e) {
+ e.kill();
+ JX.DOM.remove(node);
+ delete rows[action.key];
+ option.disabled = false;
+ });
+
+ // TODO: Grotesque.
+ action_node
+ .parentNode
+ .parentNode
+ .parentNode
+ .insertBefore(node, action_node.parentNode.parentNode.nextSibling);
+ }
+
+});
diff --git a/webroot/rsrc/js/phuix/PHUIXFormControl.js b/webroot/rsrc/js/phuix/PHUIXFormControl.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/phuix/PHUIXFormControl.js
@@ -0,0 +1,133 @@
+/**
+ * @provides phuix-form-control-view
+ * @requires javelin-install
+ * javelin-dom
+ */
+
+JX.install('PHUIXFormControl', {
+
+ members: {
+ _node: null,
+ _labelNode: null,
+ _errorNode: null,
+ _inputNode: null,
+ _valueSetCallback: null,
+ _valueGetCallback: null,
+
+ setLabel: function(label) {
+ JX.DOM.setContent(this._getLabelNode(), label);
+ return this;
+ },
+
+ setError: function(error) {
+ JX.DOM.setContent(this._getErrorNode(), error);
+ return this;
+ },
+
+ setControl: function(type, spec) {
+ var node = this._getInputNode();
+
+ var input;
+ switch (type) {
+ case 'tokenizer':
+ input = this._newTokenizer(spec);
+ break;
+ default:
+ // TODO: Default or better error?
+ JX.$E('Bad Input Type');
+ return;
+ }
+
+ JX.DOM.setContent(node, input.node);
+ this._valueGetCallback = input.get;
+ this._valueSetCallback = input.set;
+
+ return this;
+ },
+
+ setValue: function(value) {
+ this._valueSetCallback(value);
+ return this;
+ },
+
+ getValue: function() {
+ return this._valueGetCallback();
+ },
+
+ getNode: function() {
+ if (!this._node) {
+
+ var attrs = {
+ className: 'aphront-form-control grouped'
+ };
+
+ var content = [
+ this._getLabelNode(),
+ this._getErrorNode(),
+ this._getInputNode()
+ ];
+
+ this._node = JX.$N('div', attrs, content);
+ }
+
+ return this._node;
+ },
+
+ _getLabelNode: function() {
+ if (!this._labelNode) {
+ var attrs = {
+ className: 'aphront-form-label'
+ };
+
+ this._labelNode = JX.$N('label', attrs);
+ }
+
+ return this._labelNode;
+ },
+
+ _getErrorNode: function() {
+ if (!this._errorNode) {
+ var attrs = {
+ className: 'aphront-form-error'
+ };
+
+ this._errorNode = JX.$N('span', attrs);
+ }
+
+ return this._errorNode;
+ },
+
+ _getInputNode: function() {
+ if (!this._inputNode) {
+ var attrs = {
+ className: 'aphront-form-input'
+ };
+
+ this._inputNode = JX.$N('div', attrs);
+ }
+
+ return this._inputNode;
+ },
+
+ _newTokenizer: function(spec) {
+ var build = JX.Prefab.newTokenizerFromTemplate(
+ spec.markup,
+ spec.config);
+ build.tokenizer.start();
+
+ return {
+ node: build.node,
+ get: function() {
+ return JX.keys(build.tokenizer.getTokens());
+ },
+ set: function(map) {
+ for (var k in map) {
+ build.tokenizer.addToken(k, map[k]);
+ }
+ }
+ };
+ }
+
+ }
+
+});
diff --git a/webroot/rsrc/js/phuix/PHUIXIconView.js b/webroot/rsrc/js/phuix/PHUIXIconView.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/phuix/PHUIXIconView.js
@@ -0,0 +1,47 @@
+/**
+ * @provides phuix-icon-view
+ * @requires javelin-install
+ * javelin-dom
+ */
+
+JX.install('PHUIXIconView', {
+
+ members: {
+ _node: null,
+ _icon: null,
+ _color: null,
+
+ setIcon: function(icon) {
+ var node = this.getNode();
+ if (this._icon) {
+ JX.DOM.alterClass(node, this._icon, false);
+ }
+ this._icon = icon;
+ JX.DOM.alterClass(node, this._icon, true);
+ return this;
+ },
+
+ setColor: function(color) {
+ var node = this.getNode();
+ if (this._color) {
+ JX.DOM.alterClass(node, this._color, false);
+ }
+ this._color = color;
+ JX.DOM.alterClass(node, this._color, true);
+ return this;
+ },
+
+ getNode: function() {
+ if (!this._node) {
+ var attrs = {
+ className: 'phui-icon-view phui-font-fa'
+ };
+
+ this._node = JX.$N('span', attrs);
+ }
+
+ return this._node;
+ }
+ }
+
+});

File Metadata

Mime Type
text/plain
Expires
Mon, Oct 28, 3:51 AM (3 w, 12 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6721473
Default Alt Text
D14653.diff (21 KB)

Event Timeline