diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'dd913c69', + 'core.pkg.css' => 'bbeb85d2', 'core.pkg.js' => '47dc9ebb', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -25,7 +25,7 @@ 'rsrc/css/aphront/notification.css' => '9c279160', 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => 'a24cb589', - 'rsrc/css/aphront/table-view.css' => '61543e7a', + 'rsrc/css/aphront/table-view.css' => '6d01d468', 'rsrc/css/aphront/tokenizer.css' => '04875312', 'rsrc/css/aphront/tooltip.css' => '7672b60f', 'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c', @@ -498,7 +498,7 @@ 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => 'fd18389d', 'aphront-panel-view-css' => '8427b78d', - 'aphront-table-view-css' => '61543e7a', + 'aphront-table-view-css' => '6d01d468', 'aphront-tokenizer-control-css' => '04875312', 'aphront-tooltip-css' => '7672b60f', 'aphront-typeahead-control-css' => '0e403212', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1571,6 +1571,7 @@ 'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php', 'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php', 'PhabricatorApplicationEditEngine' => 'applications/transactions/editengine/PhabricatorApplicationEditEngine.php', + 'PhabricatorApplicationEditHTTPParameterHelpView' => 'applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php', 'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php', 'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php', 'PhabricatorApplicationPanelController' => 'applications/meta/controller/PhabricatorApplicationPanelController.php', @@ -5500,6 +5501,7 @@ 'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationEditEngine' => 'Phobject', + 'PhabricatorApplicationEditHTTPParameterHelpView' => 'AphrontView', 'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationLaunchView' => 'AphrontTagView', 'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController', diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -635,4 +635,8 @@ return array(); } + protected function getEditRoutePattern($base) { + return $base.'(?:(?P[0-9]\d*)/)?(?:(?Pparameters)/)?'; + } + } diff --git a/src/applications/paste/application/PhabricatorPasteApplication.php b/src/applications/paste/application/PhabricatorPasteApplication.php --- a/src/applications/paste/application/PhabricatorPasteApplication.php +++ b/src/applications/paste/application/PhabricatorPasteApplication.php @@ -39,7 +39,7 @@ '/paste/' => array( '(query/(?P[^/]+)/)?' => 'PhabricatorPasteListController', 'create/' => 'PhabricatorPasteEditController', - 'edit/(?P[1-9]\d*)/' => 'PhabricatorPasteEditController', + $this->getEditRoutePattern('edit/') => 'PhabricatorPasteEditController', 'raw/(?P[1-9]\d*)/' => 'PhabricatorPasteRawController', 'comment/(?P[1-9]\d*)/' => 'PhabricatorPasteCommentController', ), diff --git a/src/applications/paste/controller/PhabricatorPasteListController.php b/src/applications/paste/controller/PhabricatorPasteListController.php --- a/src/applications/paste/controller/PhabricatorPasteListController.php +++ b/src/applications/paste/controller/PhabricatorPasteListController.php @@ -18,7 +18,7 @@ $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Paste')) - ->setHref($this->getApplicationURI('create/')) + ->setHref($this->getApplicationURI('edit/')) ->setIcon('fa-plus-square')); return $crumbs; diff --git a/src/applications/paste/editor/PhabricatorPasteEditEngine.php b/src/applications/paste/editor/PhabricatorPasteEditEngine.php --- a/src/applications/paste/editor/PhabricatorPasteEditEngine.php +++ b/src/applications/paste/editor/PhabricatorPasteEditEngine.php @@ -41,11 +41,17 @@ id(new PhabricatorTextEditField()) ->setKey('title') ->setLabel(pht('Title')) + ->setDescription(pht('Name of the paste.')) ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE) ->setValue($object->getTitle()), id(new PhabricatorSelectEditField()) ->setKey('language') ->setLabel(pht('Language')) + ->setDescription( + pht( + 'Programming language to interpret the paste as for syntax '. + 'highlighting. By default, the language is inferred from the '. + 'title.')) ->setAliases(array('lang')) ->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE) ->setValue($object->getLanguage()) @@ -53,12 +59,14 @@ id(new PhabricatorSelectEditField()) ->setKey('status') ->setLabel(pht('Status')) + ->setDescription(pht('Archive the paste.')) ->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS) ->setValue($object->getStatus()) ->setOptions(PhabricatorPaste::getStatusNameMap()), id(new PhabricatorTextAreaEditField()) ->setKey('text') ->setLabel(pht('Text')) + ->setDescription(pht('The main body text of the paste.')) ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) ->setMonospaced(true) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) diff --git a/src/applications/transactions/editengine/PhabricatorApplicationEditEngine.php b/src/applications/transactions/editengine/PhabricatorApplicationEditEngine.php --- a/src/applications/transactions/editengine/PhabricatorApplicationEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorApplicationEditEngine.php @@ -4,6 +4,7 @@ private $viewer; private $controller; + private $isCreate; final public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -44,16 +45,22 @@ 'key' => 'policy.view', 'aliases' => array('view'), 'capability' => PhabricatorPolicyCapability::CAN_VIEW, + 'label' => pht('View Policy'), + 'description' => pht('Controls who can view the object.'), ), PhabricatorTransactions::TYPE_EDIT_POLICY => array( 'key' => 'policy.edit', 'aliases' => array('edit'), 'capability' => PhabricatorPolicyCapability::CAN_EDIT, + 'label' => pht('Edit Policy'), + 'description' => pht('Controls who can edit the object.'), ), PhabricatorTransactions::TYPE_JOIN_POLICY => array( 'key' => 'policy.join', 'aliases' => array('join'), 'capability' => PhabricatorPolicyCapability::CAN_JOIN, + 'label' => pht('Join Policy'), + 'description' => pht('Controls who can join the object.'), ), ); @@ -65,9 +72,13 @@ $capability = $spec['capability']; $key = $spec['key']; $aliases = $spec['aliases']; + $label = $spec['label']; + $description = $spec['description']; $policy_field = id(new PhabricatorPolicyEditField()) ->setKey($key) + ->setLabel($label) + ->setDescription($description) ->setAliases($aliases) ->setCapability($capability) ->setPolicies($policies) @@ -81,6 +92,9 @@ if (isset($types[$type_space])) { $space_field = id(new PhabricatorSpaceEditField()) ->setKey('spacePHID') + ->setLabel(pht('Space')) + ->setDescription( + pht('Shifts the object in the Spaces application.')) ->setAliases(array('space', 'policy.space')) ->setTransactionType($type_space) ->setValue($object->getSpacePHID()); @@ -112,6 +126,9 @@ $edge_field = id(new PhabricatorDatasourceEditField()) ->setKey('projectPHIDs') ->setLabel(pht('Projects')) + ->setDescription( + pht( + 'Add or remove associated projects.')) ->setDatasource(new PhabricatorProjectDatasource()) ->setAliases(array('project', 'projects')) ->setTransactionType($edge_type) @@ -137,6 +154,7 @@ $subscribers_field = id(new PhabricatorDatasourceEditField()) ->setKey('subscriberPHIDs') ->setLabel(pht('Subscribers')) + ->setDescription(pht('Manage subscribers.')) ->setDatasource(new PhabricatorMetaMTAMailableDatasource()) ->setAliases(array('subscriber', 'subscribers')) ->setTransactionType($subscribers_type) @@ -158,6 +176,10 @@ abstract protected function getObjectEditShortText($object); abstract protected function getObjectViewURI($object); + protected function getObjectEditURI($object) { + return $this->getController()->getApplicationURI('edit/'); + } + protected function getObjectCreateCancelURI($object) { return $this->getController()->getApplicationURI(); } @@ -174,6 +196,31 @@ return pht('Save Changes'); } + protected function getEditURI($object, $path = null) { + $parts = array( + $this->getObjectEditURI($object), + ); + + if (!$this->getIsCreate()) { + $parts[] = $object->getID().'/'; + } + + if ($path !== null) { + $parts[] = $path; + } + + return implode('', $parts); + } + + final protected function setIsCreate($is_create) { + $this->isCreate = $is_create; + return $this; + } + + final protected function getIsCreate() { + return $this->isCreate; + } + final public function buildResponse() { $controller = $this->getController(); $viewer = $this->getViewer(); @@ -194,11 +241,11 @@ return new Aphront404Response(); } - $is_create = false; + $this->setIsCreate(false); } else { $object = $this->newEditableObject(); - $is_create = true; + $this->setIsCreate(true); } $fields = $this->buildEditFields($object); @@ -209,6 +256,12 @@ ->setObject($object); } + $action = $request->getURIData('editAction'); + switch ($action) { + case 'parameters': + return $this->buildParametersResponse($object, $fields); + } + $validation_exception = null; if ($request->isFormPost()) { foreach ($fields as $field) { @@ -238,7 +291,7 @@ $validation_exception = $ex; } } else { - if ($is_create) { + if ($this->getIsCreate()) { foreach ($fields as $field) { $field->readValueFromRequest($request); } @@ -252,28 +305,28 @@ $box = id(new PHUIObjectBoxView()) ->setUser($viewer); - $crumbs = $controller->buildApplicationCrumbsForEditEngine(); + $crumbs = $this->buildCrumbs($object, $final = true); - if ($is_create) { + if ($this->getIsCreate()) { $header_text = $this->getObjectCreateTitleText($object); - $crumbs->addTextCrumb( - $this->getObjectCreateShortText($object)); - $cancel_uri = $this->getObjectCreateCancelURI($object); $submit_button = $this->getObjectCreateButtonText($object); } else { $header_text = $this->getObjectEditTitleText($object); - $crumbs->addTextCrumb( - $this->getObjectEditShortText($object), - $this->getObjectViewURI($object)); - $cancel_uri = $this->getObjectEditCancelURI($object); $submit_button = $this->getObjectEditButtonText($object); } - $box->setHeaderText($header_text); + $header = id(new PHUIHeaderView()) + ->setHeader($header_text); + + $action_button = $this->buildEditFormActionButton($object); + + $header->addActionLink($action_button); + + $box->setHeader($header); $form = id(new AphrontFormView()) ->setUser($viewer); @@ -299,5 +352,95 @@ ->appendChild($box); } + private function buildParametersResponse($object, array $fields) { + $controller = $this->getController(); + $viewer = $this->getViewer(); + $request = $controller->getRequest(); + + $crumbs = $this->buildCrumbs($object); + $crumbs->addTextCrumb(pht('HTTP Parameters')); + + $header = id(new PHUIHeaderView()) + ->setHeader( + pht( + 'HTTP Parameters: %s', + $this->getObjectCreateShortText($object))); + + // TODO: Upgrade to DocumentViewPro. + + $document = id(new PHUIDocumentView()) + ->setUser($viewer) + ->setHeader($header); + + $document->appendChild( + id(new PhabricatorApplicationEditHTTPParameterHelpView()) + ->setUser($viewer) + ->setFields($fields)); + + return $controller->newPage() + ->setTitle(pht('HTTP Parameters')) + ->setCrumbs($crumbs) + ->appendChild($document); + } + + private function buildCrumbs($object, $final = false) { + $controller = $this->getcontroller(); + + $crumbs = $controller->buildApplicationCrumbsForEditEngine(); + if ($this->getIsCreate()) { + $create_text = $this->getObjectCreateShortText($object); + if ($final) { + $crumbs->addTextCrumb($create_text); + } else { + $edit_uri = $this->getEditURI($object); + $crumbs->addTextCrumb($create_text, $edit_uri); + } + } else { + $crumbs->addTextCrumb( + $this->getObjectEditShortText($object), + $this->getObjectViewURI($object)); + + $edit_text = pht('Edit'); + if ($final) { + $crumbs->addTextCrumb($edit_text); + } else { + $edit_uri = $this->getEditURI($object); + $crumbs->addTextCrumb($edit_text, $edit_uri); + } + } + + return $crumbs; + } + + private function buildEditFormActionButton($object) { + $viewer = $this->getViewer(); + + $action_view = id(new PhabricatorActionListView()) + ->setUser($viewer); + + foreach ($this->buildEditFormActions($object) as $action) { + $action_view->addAction($action); + } + + $action_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Actions')) + ->setHref('#') + ->setIconFont('fa-bars') + ->setDropdownMenu($action_view); + + return $action_button; + } + + private function buildEditFormActions($object) { + $actions = array(); + + $actions[] = id(new PhabricatorActionView()) + ->setName(pht('Show HTTP Parameters')) + ->setIcon('fa-crosshairs') + ->setHref($this->getEditURI($object, 'parameters/')); + + return $actions; + } } 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 @@ -11,6 +11,7 @@ private $object; private $transactionType; private $metadata = array(); + private $description; public function setKey($key) { $this->key = $key; @@ -57,6 +58,15 @@ return $this->object; } + public function setDescription($description) { + $this->description = $description; + return $this; + } + + public function getDescription() { + return $this->description; + } + abstract protected function newControl(); protected function renderControl() { @@ -209,4 +219,8 @@ return $list; } + public function getHTTPParameterType() { + return 'string'; + } + } diff --git a/src/applications/transactions/editfield/PhabricatorPolicyEditField.php b/src/applications/transactions/editfield/PhabricatorPolicyEditField.php --- a/src/applications/transactions/editfield/PhabricatorPolicyEditField.php +++ b/src/applications/transactions/editfield/PhabricatorPolicyEditField.php @@ -51,4 +51,8 @@ return $control; } + public function getHTTPParameterType() { + return 'phid'; + } + } diff --git a/src/applications/transactions/editfield/PhabricatorSelectEditField.php b/src/applications/transactions/editfield/PhabricatorSelectEditField.php --- a/src/applications/transactions/editfield/PhabricatorSelectEditField.php +++ b/src/applications/transactions/editfield/PhabricatorSelectEditField.php @@ -22,4 +22,8 @@ ->setOptions($this->getOptions()); } + public function getHTTPParameterType() { + return 'select'; + } + } diff --git a/src/applications/transactions/editfield/PhabricatorSpaceEditField.php b/src/applications/transactions/editfield/PhabricatorSpaceEditField.php --- a/src/applications/transactions/editfield/PhabricatorSpaceEditField.php +++ b/src/applications/transactions/editfield/PhabricatorSpaceEditField.php @@ -9,4 +9,8 @@ return null; } + public function getHTTPParameterType() { + return 'phid'; + } + } 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 @@ -87,4 +87,8 @@ return $new; } + public function getHTTPParameterType() { + return 'list'; + } + } diff --git a/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php b/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php new file mode 100644 --- /dev/null +++ b/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php @@ -0,0 +1,336 @@ +object = $object; + return $this; + } + + public function getObject() { + return $this->object; + } + + public function setFields(array $fields) { + $this->fields = $fields; + return $this; + } + + public function getFields() { + return $this->fields; + } + + public function render() { + $object = $this->getObject(); + $fields = $this->getFields(); + + $uri = 'https://your.install.com/application/edit/'; + + // Remove fields which do not expose an HTTP parameter type. + $types = array(); + foreach ($fields as $key => $field) { + $type = $field->getHTTPParameterType(); + if ($type === null) { + unset($fields[$key]); + } + $types[$type][] = $field; + } + + $intro = pht(<<getLabel(), + $field->getKey(), + $field->getHTTPParameterType(), + $field->getDescription(), + ); + } + + $main_table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Label'), + pht('Key'), + pht('Type'), + pht('Description'), + )) + ->setColumnClasses( + array( + 'pri', + null, + null, + 'wide', + )); + + $aliases_text = pht(<<getAliases(); + if (!$aliases) { + continue; + } + $rows[] = array( + $field->getLabel(), + $field->getKey(), + implode(', ', $aliases), + ); + } + + $alias_table = id(new AphrontTableView($rows)) + ->setNoDataString(pht('This object has no fields with aliases.')) + ->setHeaders( + array( + pht('Label'), + pht('Key'), + pht('Aliases'), + )) + ->setColumnClasses( + array( + 'pri', + null, + 'wide', + )); + + $select_text = pht(<<getOptions(); + $label = $field->getLabel(); + foreach ($options as $option_key => $option_value) { + if (strlen($option_key)) { + $option_display = $option_key; + } else { + $option_display = phutil_tag('em', array(), pht('')); + } + + $rows[] = array( + $label, + $option_display, + $option_value, + ); + $label = null; + } + } + + $select_table = id(new AphrontTableView($rows)) + ->setNoDataString(pht('This object has no select fields.')) + ->setHeaders( + array( + pht('Field'), + pht('Value'), + pht('Label'), + )) + ->setColumnClasses( + array( + 'pri', + null, + 'wide', + )); + + $types_text = pht(<< array( + 'format' => pht('URL encoded text.'), + 'examples' => array( + 'v=simple', + 'v=properly%20escaped%20text', + ), + ), + 'select' => array( + 'format' => pht('Value from allowed set.'), + 'examples' => array( + 'v=value', + ), + ), + 'list' => array( + 'format' => array( + pht('Comma-separated list of PHIDs.'), + pht('List of PHIDs, as array.'), + ), + 'examples' => array( + 'v=PHID-XXXX-1111,PHID-XXXX-2222', + 'v[]=PHID-XXXX-1111&v[]=PHID-XXXX-2222', + ), + ), + 'phid' => array( + 'format' => pht('Single PHID.'), + 'examples' => pht('v=PHID-XXX-1111'), + ), + ); + + $rows = array(); + $br = phutil_tag('br'); + foreach ($types as $type => $fields) { + $spec = idx($type_spec, $type, array()); + + $field_list = mpull($fields, 'getKey'); + $field_list = phutil_implode_html($br, $field_list); + + $format_list = idx($spec, 'format', array()); + $format_list = phutil_implode_html($br, (array)$format_list); + + $example_list = idx($spec, 'examples', array()); + $example_list = phutil_implode_html($br, (array)$example_list); + + $rows[] = array( + $type, + $field_list, + $format_list, + $example_list, + ); + } + + $types_table = id(new AphrontTableView($rows)) + ->setNoDataString(pht('This object has no fields with types.')) + ->setHeaders( + array( + pht('Type'), + pht('Fields'), + pht('Formats'), + pht('Examples'), + )) + ->setColumnClasses( + array( + 'pri top', + 'top', + 'top', + 'wide top prewrap', + )); + + return array( + $this->renderInstructions($intro), + $this->renderTable($main_table), + $this->renderInstructions($aliases_text), + $this->renderTable($alias_table), + $this->renderInstructions($select_text), + $this->renderTable($select_table), + $this->renderInstructions($types_text), + $this->renderTable($types_table), + ); + } + + protected function renderTable(AphrontTableView $table) { + return id(new PHUIBoxView()) + ->addMargin(PHUI::MARGIN_LARGE_LEFT) + ->addMargin(PHUI::MARGIN_LARGE_RIGHT) + ->addMargin(PHUI::MARGIN_LARGE_BOTTOM) + ->appendChild($table); + } + + protected function renderInstructions($corpus) { + $viewer = $this->getUser(); + + return id(new PHUIBoxView()) + ->addMargin(PHUI::MARGIN_SMALL_TOP) + ->addMargin(PHUI::MARGIN_SMALL_BOTTOM) + ->appendChild(new PHUIRemarkupView($viewer, $corpus)); + } + +} diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -155,6 +155,10 @@ color: {$darkbluetext}; } +.aphront-table-view td.top { + vertical-align: top; +} + .aphront-table-view td.wide { white-space: normal; width: 100%;