mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-22 21:40:55 +01:00
Restore "Reclaim" and "Abandon" actions to Differential on EditEngine
Summary: Ref T11114. This begins restoring comment actions to Differential, but on top of EditEngine. Test Plan: {F2263148} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11114 Differential Revision: https://secure.phabricator.com/D17107
This commit is contained in:
parent
c05306d746
commit
3c5a17ba8a
11 changed files with 343 additions and 10 deletions
|
@ -9,7 +9,7 @@ return array(
|
|||
'names' => array(
|
||||
'conpherence.pkg.css' => '0b64e988',
|
||||
'conpherence.pkg.js' => '6249a1cf',
|
||||
'core.pkg.css' => '404132bb',
|
||||
'core.pkg.css' => '202700e2',
|
||||
'core.pkg.js' => '28e8cda8',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => 'a4ba74b5',
|
||||
|
@ -146,7 +146,7 @@ return array(
|
|||
'rsrc/css/phui/phui-document.css' => 'c32e8dec',
|
||||
'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9',
|
||||
'rsrc/css/phui/phui-fontkit.css' => '9cda225e',
|
||||
'rsrc/css/phui/phui-form-view.css' => 'cd79ff6a',
|
||||
'rsrc/css/phui/phui-form-view.css' => '04cc4771',
|
||||
'rsrc/css/phui/phui-form.css' => '2342b0e5',
|
||||
'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
|
||||
'rsrc/css/phui/phui-header-view.css' => '6ec8f155',
|
||||
|
@ -542,7 +542,7 @@ return array(
|
|||
'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262',
|
||||
'rsrc/js/phuix/PHUIXAutocomplete.js' => '6d86ce8b',
|
||||
'rsrc/js/phuix/PHUIXDropdownMenu.js' => '82e270da',
|
||||
'rsrc/js/phuix/PHUIXFormControl.js' => '301b7812',
|
||||
'rsrc/js/phuix/PHUIXFormControl.js' => 'bbece68d',
|
||||
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
|
||||
),
|
||||
'symbols' => array(
|
||||
|
@ -860,7 +860,7 @@ return array(
|
|||
'phui-font-icon-base-css' => '870a7360',
|
||||
'phui-fontkit-css' => '9cda225e',
|
||||
'phui-form-css' => '2342b0e5',
|
||||
'phui-form-view-css' => 'cd79ff6a',
|
||||
'phui-form-view-css' => '04cc4771',
|
||||
'phui-head-thing-view-css' => 'fd311e5f',
|
||||
'phui-header-view-css' => '6ec8f155',
|
||||
'phui-hovercard' => '1bd28176',
|
||||
|
@ -901,7 +901,7 @@ return array(
|
|||
'phuix-action-view' => '8cf6d262',
|
||||
'phuix-autocomplete' => '6d86ce8b',
|
||||
'phuix-dropdown-menu' => '82e270da',
|
||||
'phuix-form-control-view' => '301b7812',
|
||||
'phuix-form-control-view' => 'bbece68d',
|
||||
'phuix-icon-view' => 'bff6884b',
|
||||
'policy-css' => '957ea14c',
|
||||
'policy-edit-css' => '815c66f7',
|
||||
|
@ -1159,10 +1159,6 @@ return array(
|
|||
'2ee659ce' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
'301b7812' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
),
|
||||
'320810c8' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
|
@ -1916,6 +1912,10 @@ return array(
|
|||
'javelin-vector',
|
||||
'javelin-install',
|
||||
),
|
||||
'bbece68d' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
),
|
||||
'bcaccd64' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-behavior-device',
|
||||
|
|
|
@ -505,6 +505,8 @@ phutil_register_library_map(array(
|
|||
'DifferentialReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersHeraldAction.php',
|
||||
'DifferentialReviewersView' => 'applications/differential/view/DifferentialReviewersView.php',
|
||||
'DifferentialRevision' => 'applications/differential/storage/DifferentialRevision.php',
|
||||
'DifferentialRevisionAbandonTransaction' => 'applications/differential/xaction/DifferentialRevisionAbandonTransaction.php',
|
||||
'DifferentialRevisionActionTransaction' => 'applications/differential/xaction/DifferentialRevisionActionTransaction.php',
|
||||
'DifferentialRevisionAffectedFilesHeraldField' => 'applications/differential/herald/DifferentialRevisionAffectedFilesHeraldField.php',
|
||||
'DifferentialRevisionAuthorHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorHeraldField.php',
|
||||
'DifferentialRevisionAuthorProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php',
|
||||
|
@ -539,6 +541,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionPackageHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageHeraldField.php',
|
||||
'DifferentialRevisionPackageOwnerHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageOwnerHeraldField.php',
|
||||
'DifferentialRevisionQuery' => 'applications/differential/query/DifferentialRevisionQuery.php',
|
||||
'DifferentialRevisionReclaimTransaction' => 'applications/differential/xaction/DifferentialRevisionReclaimTransaction.php',
|
||||
'DifferentialRevisionRelationship' => 'applications/differential/relationships/DifferentialRevisionRelationship.php',
|
||||
'DifferentialRevisionRelationshipSource' => 'applications/search/relationship/DifferentialRevisionRelationshipSource.php',
|
||||
'DifferentialRevisionRepositoryHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryHeraldField.php',
|
||||
|
@ -1840,6 +1843,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationsApplication' => 'applications/meta/application/PhabricatorApplicationsApplication.php',
|
||||
'PhabricatorApplicationsController' => 'applications/meta/controller/PhabricatorApplicationsController.php',
|
||||
'PhabricatorApplicationsListController' => 'applications/meta/controller/PhabricatorApplicationsListController.php',
|
||||
'PhabricatorApplyEditField' => 'applications/transactions/editfield/PhabricatorApplyEditField.php',
|
||||
'PhabricatorAsanaAuthProvider' => 'applications/auth/provider/PhabricatorAsanaAuthProvider.php',
|
||||
'PhabricatorAsanaConfigOptions' => 'applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php',
|
||||
'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorAsanaSubtaskHasObjectEdgeType.php',
|
||||
|
@ -2572,6 +2576,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEditEngineSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineSearchEngine.php',
|
||||
'PhabricatorEditEngineSelectCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineSelectCommentAction.php',
|
||||
'PhabricatorEditEngineSettingsPanel' => 'applications/settings/panel/PhabricatorEditEngineSettingsPanel.php',
|
||||
'PhabricatorEditEngineStaticCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineStaticCommentAction.php',
|
||||
'PhabricatorEditEngineTokenizerCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineTokenizerCommentAction.php',
|
||||
'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php',
|
||||
'PhabricatorEditPage' => 'applications/transactions/editengine/PhabricatorEditPage.php',
|
||||
|
@ -5169,6 +5174,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFulltextInterface',
|
||||
'PhabricatorConduitResultInterface',
|
||||
),
|
||||
'DifferentialRevisionAbandonTransaction' => 'DifferentialRevisionActionTransaction',
|
||||
'DifferentialRevisionActionTransaction' => 'DifferentialRevisionTransactionType',
|
||||
'DifferentialRevisionAffectedFilesHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
'DifferentialRevisionAuthorHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
'DifferentialRevisionAuthorProjectsHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
|
@ -5203,6 +5210,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionPackageHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
'DifferentialRevisionPackageOwnerHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
'DifferentialRevisionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'DifferentialRevisionReclaimTransaction' => 'DifferentialRevisionActionTransaction',
|
||||
'DifferentialRevisionRelationship' => 'PhabricatorObjectRelationship',
|
||||
'DifferentialRevisionRelationshipSource' => 'PhabricatorObjectRelationshipSource',
|
||||
'DifferentialRevisionRepositoryHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
|
@ -6688,6 +6696,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationsApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationsController' => 'PhabricatorController',
|
||||
'PhabricatorApplicationsListController' => 'PhabricatorApplicationsController',
|
||||
'PhabricatorApplyEditField' => 'PhabricatorEditField',
|
||||
'PhabricatorAsanaAuthProvider' => 'PhabricatorOAuth2AuthProvider',
|
||||
'PhabricatorAsanaConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'PhabricatorEdgeType',
|
||||
|
@ -7545,6 +7554,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEditEngineSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorEditEngineSelectCommentAction' => 'PhabricatorEditEngineCommentAction',
|
||||
'PhabricatorEditEngineSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||
'PhabricatorEditEngineStaticCommentAction' => 'PhabricatorEditEngineCommentAction',
|
||||
'PhabricatorEditEngineTokenizerCommentAction' => 'PhabricatorEditEngineCommentAction',
|
||||
'PhabricatorEditField' => 'Phobject',
|
||||
'PhabricatorEditPage' => 'Phobject',
|
||||
|
|
|
@ -87,6 +87,7 @@ final class DifferentialRevisionEditEngine
|
|||
}
|
||||
|
||||
protected function buildCustomEditFields($object) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$plan_required = PhabricatorEnv::getEnvConfig(
|
||||
'differential.require-test-plan-field');
|
||||
|
@ -180,7 +181,7 @@ final class DifferentialRevisionEditEngine
|
|||
->setUseEdgeTransactions(true)
|
||||
->setTransactionType(
|
||||
DifferentialRevisionReviewersTransaction::TRANSACTIONTYPE)
|
||||
->setCommentActionLabel(pht('Edit Reviewers'))
|
||||
->setCommentActionLabel(pht('Change Reviewers'))
|
||||
->setDescription(pht('Reviewers for this revision.'))
|
||||
->setConduitDescription(pht('Change the reviewers for this revision.'))
|
||||
->setConduitTypeDescription(pht('New reviewers.'))
|
||||
|
@ -212,6 +213,11 @@ final class DifferentialRevisionEditEngine
|
|||
->setConduitTypeDescription(pht('List of tasks.'))
|
||||
->setValue(array());
|
||||
|
||||
$actions = DifferentialRevisionActionTransaction::loadAllActions();
|
||||
foreach ($actions as $key => $action) {
|
||||
$fields[] = $action->newEditField($object, $viewer);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
|
|
|
@ -442,6 +442,11 @@ final class DifferentialRevision extends DifferentialDAO
|
|||
return DifferentialRevisionStatus::isClosedStatus($this->getStatus());
|
||||
}
|
||||
|
||||
public function isAbandoned() {
|
||||
$status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED;
|
||||
return ($this->getStatus() == $status_abandoned);
|
||||
}
|
||||
|
||||
public function getStatusIcon() {
|
||||
$map = array(
|
||||
ArcanistDifferentialRevisionStatus::NEEDS_REVIEW
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialRevisionAbandonTransaction
|
||||
extends DifferentialRevisionActionTransaction {
|
||||
|
||||
const TRANSACTIONTYPE = 'differential.revision.abandon';
|
||||
const ACTIONKEY = 'abandon';
|
||||
|
||||
protected function getRevisionActionLabel() {
|
||||
return pht('Abandon Revision');
|
||||
}
|
||||
|
||||
protected function getRevisionActionDescription() {
|
||||
return pht('This revision will be abandoned and closed.');
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'fa-plane';
|
||||
}
|
||||
|
||||
public function getColor() {
|
||||
return 'indigo';
|
||||
}
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->isAbandoned();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED);
|
||||
}
|
||||
|
||||
protected function validateAction($object, PhabricatorUser $viewer) {
|
||||
if ($object->isClosed()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'You can not abandon this revision because it has already been '.
|
||||
'closed. Only open revisions can be abandoned.'));
|
||||
}
|
||||
|
||||
$config_key = 'differential.always-allow-abandon';
|
||||
if (!PhabricatorEnv::getEnvConfig($config_key)) {
|
||||
if (!$this->isViewerRevisionAuthor($object, $viewer)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'You can not abandon this revision because you are not the '.
|
||||
'author. You can only abandon revisions you own. You can change '.
|
||||
'this behavior by adjusting the "%s" setting in Config',
|
||||
$config_key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s abandoned this revision.',
|
||||
$this->renderAuthor());
|
||||
}
|
||||
|
||||
public function getTitleForFeed() {
|
||||
return pht(
|
||||
'%s abandoned %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
abstract class DifferentialRevisionActionTransaction
|
||||
extends DifferentialRevisionTransactionType {
|
||||
|
||||
final public function getRevisionActionKey() {
|
||||
return $this->getPhobjectClassConstant('ACTIONKEY', 32);
|
||||
}
|
||||
|
||||
public function isActionAvailable($object, PhabricatorUser $viewer) {
|
||||
try {
|
||||
$this->validateAction($object, $viewer);
|
||||
return true;
|
||||
} catch (Exception $ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected function validateAction($object, PhabricatorUser $viewer);
|
||||
abstract protected function getRevisionActionLabel();
|
||||
|
||||
protected function getRevisionActionDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function loadAllActions() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->setUniqueMethod('getRevisionActionKey')
|
||||
->execute();
|
||||
}
|
||||
|
||||
protected function isViewerRevisionAuthor(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
|
||||
if (!$viewer->getPHID()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ($viewer->getPHID() === $revision->getAuthorPHID());
|
||||
}
|
||||
|
||||
public function newEditField(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
|
||||
$field = id(new PhabricatorApplyEditField())
|
||||
->setKey($this->getRevisionActionKey())
|
||||
->setTransactionType($this->getTransactionTypeConstant())
|
||||
->setValue(true);
|
||||
|
||||
if ($this->isActionAvailable($revision, $viewer)) {
|
||||
$label = $this->getRevisionActionLabel();
|
||||
if ($label !== null) {
|
||||
$field->setCommentActionLabel($label);
|
||||
|
||||
$description = $this->getRevisionActionDescription();
|
||||
$field->setActionDescription($description);
|
||||
}
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
$errors = array();
|
||||
$actor = $this->getActor();
|
||||
|
||||
$action_exception = null;
|
||||
try {
|
||||
$this->validateAction($object, $actor);
|
||||
} catch (Exception $ex) {
|
||||
$action_exception = $ex;
|
||||
}
|
||||
|
||||
foreach ($xactions as $xaction) {
|
||||
if ($action_exception) {
|
||||
$errors[] = $this->newInvalidError(
|
||||
$action_exception->getMessage(),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialRevisionReclaimTransaction
|
||||
extends DifferentialRevisionActionTransaction {
|
||||
|
||||
const TRANSACTIONTYPE = 'differential.revision.reclaim';
|
||||
const ACTIONKEY = 'reclaim';
|
||||
|
||||
protected function getRevisionActionLabel() {
|
||||
return pht('Reclaim Revision');
|
||||
}
|
||||
|
||||
protected function getRevisionActionDescription() {
|
||||
return pht('This revision will be reclaimed and reopened.');
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'fa-bullhorn';
|
||||
}
|
||||
|
||||
public function getColor() {
|
||||
return 'sky';
|
||||
}
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return !$object->isAbandoned();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
|
||||
}
|
||||
|
||||
protected function validateAction($object, PhabricatorUser $viewer) {
|
||||
if (!$object->isAbandoned()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'You can not reclaim this revision because it has not been '.
|
||||
'abandoned. Only abandoned revisions can be reclaimed.'));
|
||||
}
|
||||
|
||||
if (!$this->isViewerRevisionAuthor($object, $viewer)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'You can not reclaim this revision because you are not the '.
|
||||
'revision author. You can only reclaim revisions you own.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s reclaimed this revision.',
|
||||
$this->renderAuthor());
|
||||
}
|
||||
|
||||
public function getTitleForFeed() {
|
||||
return pht(
|
||||
'%s reclaimed %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorEditEngineStaticCommentAction
|
||||
extends PhabricatorEditEngineCommentAction {
|
||||
|
||||
private $description;
|
||||
|
||||
public function setDescription($description) {
|
||||
$this->description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescription() {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getPHUIXControlType() {
|
||||
return 'static';
|
||||
}
|
||||
|
||||
public function getPHUIXControlSpecification() {
|
||||
return array(
|
||||
'value' => $this->getValue(),
|
||||
'description' => $this->getDescription(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorApplyEditField
|
||||
extends PhabricatorEditField {
|
||||
|
||||
private $actionDescription;
|
||||
|
||||
protected function newControl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setActionDescription($action_description) {
|
||||
$this->actionDescription = $action_description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getActionDescription() {
|
||||
return $this->actionDescription;
|
||||
}
|
||||
|
||||
protected function newHTTPParameterType() {
|
||||
return new AphrontBoolHTTPParameterType();
|
||||
}
|
||||
|
||||
protected function newConduitParameterType() {
|
||||
return new ConduitBoolParameterType();
|
||||
}
|
||||
|
||||
public function shouldGenerateTransactionsFromSubmit() {
|
||||
// This type of edit field just applies a prebuilt action, like "Accept
|
||||
// Revision", and can not be submitted as part of an "Edit Object" form.
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function newCommentAction() {
|
||||
return id(new PhabricatorEditEngineStaticCommentAction())
|
||||
->setDescription($this->getActionDescription());
|
||||
}
|
||||
|
||||
}
|
|
@ -545,3 +545,8 @@ properly, and submit values. */
|
|||
.device-desktop .aphront-form-error .phui-icon-view:hover {
|
||||
color: {$red};
|
||||
}
|
||||
|
||||
.phui-form-static-action {
|
||||
padding: 4px;
|
||||
color: {$bluetext};
|
||||
}
|
||||
|
|
|
@ -47,6 +47,9 @@ JX.install('PHUIXFormControl', {
|
|||
case 'optgroups':
|
||||
input = this._newOptgroups(spec);
|
||||
break;
|
||||
case 'static':
|
||||
input = this._newStatic(spec);
|
||||
break;
|
||||
default:
|
||||
// TODO: Default or better error?
|
||||
JX.$E('Bad Input Type');
|
||||
|
@ -172,6 +175,25 @@ JX.install('PHUIXFormControl', {
|
|||
};
|
||||
},
|
||||
|
||||
_newStatic: function(spec) {
|
||||
var node = JX.$N(
|
||||
'div',
|
||||
{
|
||||
className: 'phui-form-static-action'
|
||||
},
|
||||
spec.description || '');
|
||||
|
||||
return {
|
||||
node: node,
|
||||
get: function() {
|
||||
return true;
|
||||
},
|
||||
set: function() {
|
||||
return;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
_newPoints: function(spec) {
|
||||
var attrs = {
|
||||
type: 'text',
|
||||
|
|
Loading…
Reference in a new issue