Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14011759
D14653.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
21 KB
Referenced Files
None
Subscribers
None
D14653.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Sat, Nov 2, 5:13 AM (2 w, 2 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6721473
Default Alt Text
D14653.diff (21 KB)
Attached To
Mode
D14653: Basic stacked action support for EditEngine
Attached
Detach File
Event Timeline
Log In to Comment