mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 16:52:41 +01:00
Allow action list items to render as forms
Summary: In some cases, we want an action item (like "Subscribe") to effect a write that needs a CSRF check. Allow such items to render as forms so they gracefully degrade if JS is FUBAR'd. D3499 has a specific example. Test Plan: Loaded new UI example page, clicked all the actions. Reviewers: btrahan, vrana, avivey Reviewed By: btrahan CC: aran Differential Revision: https://secure.phabricator.com/D3596
This commit is contained in:
parent
62d431fd7c
commit
087c328f89
9 changed files with 221 additions and 43 deletions
|
@ -2280,7 +2280,7 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'phabricator-action-list-view-css' =>
|
'phabricator-action-list-view-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/1b4eef71/rsrc/css/layout/phabricator-action-list-view.css',
|
'uri' => '/res/fa304592/rsrc/css/layout/phabricator-action-list-view.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
|
@ -2325,7 +2325,7 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'phabricator-core-buttons-css' =>
|
'phabricator-core-buttons-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/a105abeb/rsrc/css/core/buttons.css',
|
'uri' => '/res/427fac91/rsrc/css/core/buttons.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
|
@ -3003,7 +3003,7 @@ celerity_register_resource_map(array(
|
||||||
), array(
|
), array(
|
||||||
'packages' =>
|
'packages' =>
|
||||||
array(
|
array(
|
||||||
'495635fb' =>
|
'19bef443' =>
|
||||||
array(
|
array(
|
||||||
'name' => 'core.pkg.css',
|
'name' => 'core.pkg.css',
|
||||||
'symbols' =>
|
'symbols' =>
|
||||||
|
@ -3032,7 +3032,7 @@ celerity_register_resource_map(array(
|
||||||
21 => 'phabricator-flag-css',
|
21 => 'phabricator-flag-css',
|
||||||
22 => 'aphront-error-view-css',
|
22 => 'aphront-error-view-css',
|
||||||
),
|
),
|
||||||
'uri' => '/res/pkg/495635fb/core.pkg.css',
|
'uri' => '/res/pkg/19bef443/core.pkg.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
),
|
),
|
||||||
'3a455e4f' =>
|
'3a455e4f' =>
|
||||||
|
@ -3199,20 +3199,20 @@ celerity_register_resource_map(array(
|
||||||
'reverse' =>
|
'reverse' =>
|
||||||
array(
|
array(
|
||||||
'aphront-attached-file-view-css' => '7839ae2d',
|
'aphront-attached-file-view-css' => '7839ae2d',
|
||||||
'aphront-crumbs-view-css' => '495635fb',
|
'aphront-crumbs-view-css' => '19bef443',
|
||||||
'aphront-dialog-view-css' => '495635fb',
|
'aphront-dialog-view-css' => '19bef443',
|
||||||
'aphront-error-view-css' => '495635fb',
|
'aphront-error-view-css' => '19bef443',
|
||||||
'aphront-form-view-css' => '495635fb',
|
'aphront-form-view-css' => '19bef443',
|
||||||
'aphront-headsup-action-list-view-css' => '2ba14b3d',
|
'aphront-headsup-action-list-view-css' => '2ba14b3d',
|
||||||
'aphront-headsup-view-css' => '495635fb',
|
'aphront-headsup-view-css' => '19bef443',
|
||||||
'aphront-list-filter-view-css' => '495635fb',
|
'aphront-list-filter-view-css' => '19bef443',
|
||||||
'aphront-pager-view-css' => '495635fb',
|
'aphront-pager-view-css' => '19bef443',
|
||||||
'aphront-panel-view-css' => '495635fb',
|
'aphront-panel-view-css' => '19bef443',
|
||||||
'aphront-side-nav-view-css' => '495635fb',
|
'aphront-side-nav-view-css' => '19bef443',
|
||||||
'aphront-table-view-css' => '495635fb',
|
'aphront-table-view-css' => '19bef443',
|
||||||
'aphront-tokenizer-control-css' => '495635fb',
|
'aphront-tokenizer-control-css' => '19bef443',
|
||||||
'aphront-tooltip-css' => '495635fb',
|
'aphront-tooltip-css' => '19bef443',
|
||||||
'aphront-typeahead-control-css' => '495635fb',
|
'aphront-typeahead-control-css' => '19bef443',
|
||||||
'differential-changeset-view-css' => '2ba14b3d',
|
'differential-changeset-view-css' => '2ba14b3d',
|
||||||
'differential-core-view-css' => '2ba14b3d',
|
'differential-core-view-css' => '2ba14b3d',
|
||||||
'differential-inline-comment-editor' => 'd05e3c0f',
|
'differential-inline-comment-editor' => 'd05e3c0f',
|
||||||
|
@ -3278,15 +3278,15 @@ celerity_register_resource_map(array(
|
||||||
'javelin-workflow' => '3a455e4f',
|
'javelin-workflow' => '3a455e4f',
|
||||||
'maniphest-task-summary-css' => '7839ae2d',
|
'maniphest-task-summary-css' => '7839ae2d',
|
||||||
'maniphest-transaction-detail-css' => '7839ae2d',
|
'maniphest-transaction-detail-css' => '7839ae2d',
|
||||||
'phabricator-app-buttons-css' => '495635fb',
|
'phabricator-app-buttons-css' => '19bef443',
|
||||||
'phabricator-content-source-view-css' => '2ba14b3d',
|
'phabricator-content-source-view-css' => '2ba14b3d',
|
||||||
'phabricator-core-buttons-css' => '495635fb',
|
'phabricator-core-buttons-css' => '19bef443',
|
||||||
'phabricator-core-css' => '495635fb',
|
'phabricator-core-css' => '19bef443',
|
||||||
'phabricator-directory-css' => '495635fb',
|
'phabricator-directory-css' => '19bef443',
|
||||||
'phabricator-drag-and-drop-file-upload' => 'd05e3c0f',
|
'phabricator-drag-and-drop-file-upload' => 'd05e3c0f',
|
||||||
'phabricator-dropdown-menu' => '3a455e4f',
|
'phabricator-dropdown-menu' => '3a455e4f',
|
||||||
'phabricator-flag-css' => '495635fb',
|
'phabricator-flag-css' => '19bef443',
|
||||||
'phabricator-jump-nav' => '495635fb',
|
'phabricator-jump-nav' => '19bef443',
|
||||||
'phabricator-keyboard-shortcut' => '3a455e4f',
|
'phabricator-keyboard-shortcut' => '3a455e4f',
|
||||||
'phabricator-keyboard-shortcut-manager' => '3a455e4f',
|
'phabricator-keyboard-shortcut-manager' => '3a455e4f',
|
||||||
'phabricator-menu-item' => '3a455e4f',
|
'phabricator-menu-item' => '3a455e4f',
|
||||||
|
@ -3294,11 +3294,11 @@ celerity_register_resource_map(array(
|
||||||
'phabricator-paste-file-upload' => '3a455e4f',
|
'phabricator-paste-file-upload' => '3a455e4f',
|
||||||
'phabricator-prefab' => '3a455e4f',
|
'phabricator-prefab' => '3a455e4f',
|
||||||
'phabricator-project-tag-css' => '7839ae2d',
|
'phabricator-project-tag-css' => '7839ae2d',
|
||||||
'phabricator-remarkup-css' => '495635fb',
|
'phabricator-remarkup-css' => '19bef443',
|
||||||
'phabricator-shaped-request' => 'd05e3c0f',
|
'phabricator-shaped-request' => 'd05e3c0f',
|
||||||
'phabricator-standard-page-view' => '495635fb',
|
'phabricator-standard-page-view' => '19bef443',
|
||||||
'phabricator-tooltip' => '3a455e4f',
|
'phabricator-tooltip' => '3a455e4f',
|
||||||
'phabricator-transaction-view-css' => '495635fb',
|
'phabricator-transaction-view-css' => '19bef443',
|
||||||
'syntax-highlighting-css' => '495635fb',
|
'syntax-highlighting-css' => '19bef443',
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
|
@ -545,6 +545,7 @@ phutil_register_library_map(array(
|
||||||
'PackageModifyMail' => 'applications/owners/mail/PackageModifyMail.php',
|
'PackageModifyMail' => 'applications/owners/mail/PackageModifyMail.php',
|
||||||
'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php',
|
'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php',
|
||||||
'PhabricatorAccessLog' => 'infrastructure/PhabricatorAccessLog.php',
|
'PhabricatorAccessLog' => 'infrastructure/PhabricatorAccessLog.php',
|
||||||
|
'PhabricatorActionListExample' => 'applications/uiexample/examples/PhabricatorActionListExample.php',
|
||||||
'PhabricatorActionListView' => 'view/layout/PhabricatorActionListView.php',
|
'PhabricatorActionListView' => 'view/layout/PhabricatorActionListView.php',
|
||||||
'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php',
|
'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php',
|
||||||
'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php',
|
'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php',
|
||||||
|
@ -1710,6 +1711,7 @@ phutil_register_library_map(array(
|
||||||
'PackageDeleteMail' => 'PackageMail',
|
'PackageDeleteMail' => 'PackageMail',
|
||||||
'PackageModifyMail' => 'PackageMail',
|
'PackageModifyMail' => 'PackageMail',
|
||||||
'Phabricator404Controller' => 'PhabricatorController',
|
'Phabricator404Controller' => 'PhabricatorController',
|
||||||
|
'PhabricatorActionListExample' => 'PhabricatorUIExample',
|
||||||
'PhabricatorActionListView' => 'AphrontView',
|
'PhabricatorActionListView' => 'AphrontView',
|
||||||
'PhabricatorActionView' => 'AphrontView',
|
'PhabricatorActionView' => 'AphrontView',
|
||||||
'PhabricatorAnchorView' => 'AphrontView',
|
'PhabricatorAnchorView' => 'AphrontView',
|
||||||
|
|
|
@ -34,6 +34,12 @@ final class PhabricatorFlagsUIEventListener extends PhutilEventListener {
|
||||||
$user = $event->getUser();
|
$user = $event->getUser();
|
||||||
$object = $event->getValue('object');
|
$object = $event->getValue('object');
|
||||||
|
|
||||||
|
if (!$object || !$object->getPHID()) {
|
||||||
|
// If we have no object, or the object doesn't have a PHID yet, we can't
|
||||||
|
// flag it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$flag = PhabricatorFlagQuery::loadUserFlag($user, $object->getPHID());
|
$flag = PhabricatorFlagQuery::loadUserFlag($user, $object->getPHID());
|
||||||
|
|
||||||
if ($flag) {
|
if ($flag) {
|
||||||
|
|
|
@ -53,6 +53,12 @@ final class PhabricatorUIExampleRenderController extends PhabricatorController {
|
||||||
$example = $classes[$selected];
|
$example = $classes[$selected];
|
||||||
$example->setRequest($this->getRequest());
|
$example->setRequest($this->getRequest());
|
||||||
|
|
||||||
|
$result = $example->renderExample();
|
||||||
|
if ($result instanceof AphrontResponse) {
|
||||||
|
// This allows examples to generate dialogs, etc., for demonstration.
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
$nav->appendChild(
|
$nav->appendChild(
|
||||||
'<div class="phabricator-ui-example-header">'.
|
'<div class="phabricator-ui-example-header">'.
|
||||||
'<h1 class="phabricator-ui-example-name">'.
|
'<h1 class="phabricator-ui-example-name">'.
|
||||||
|
@ -64,7 +70,7 @@ final class PhabricatorUIExampleRenderController extends PhabricatorController {
|
||||||
'</p>'.
|
'</p>'.
|
||||||
'</div>');
|
'</div>');
|
||||||
|
|
||||||
$nav->appendChild($example->renderExample());
|
$nav->appendChild($result);
|
||||||
|
|
||||||
return $this->buildApplicationPage(
|
return $this->buildApplicationPage(
|
||||||
$nav,
|
$nav,
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
final class PhabricatorActionListExample extends PhabricatorUIExample {
|
||||||
|
|
||||||
|
public function getName() {
|
||||||
|
return 'Action List';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription() {
|
||||||
|
return 'Use <tt>PhabricatorActionListView</tt> to render object actions.';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderExample() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$user = $request->getUser();
|
||||||
|
|
||||||
|
$notices = array();
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$notices[] = 'You just submitted a valid form POST.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->isJavelinWorkflow()) {
|
||||||
|
$notices[] = 'You just submitted a Workflow request.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($notices) {
|
||||||
|
$notices = id(new AphrontErrorView())
|
||||||
|
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
|
||||||
|
->setErrors($notices);
|
||||||
|
} else {
|
||||||
|
$notices = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->isJavelinWorkflow()) {
|
||||||
|
$dialog = new AphrontDialogView();
|
||||||
|
$dialog->setUser($user);
|
||||||
|
$dialog->setTitle('Request Information');
|
||||||
|
$dialog->appendChild($notices);
|
||||||
|
$dialog->addCancelButton($request->getRequestURI(), 'Close');
|
||||||
|
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
$view = new PhabricatorActionListView();
|
||||||
|
$view->setUser($user);
|
||||||
|
|
||||||
|
$view->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setUser($user)
|
||||||
|
->setHref($request->getRequestURI())
|
||||||
|
->setName('Normal Action')
|
||||||
|
->setIcon('file'));
|
||||||
|
|
||||||
|
$view->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setUser($user)
|
||||||
|
->setHref($request->getRequestURI())
|
||||||
|
->setDisabled(true)
|
||||||
|
->setName('Disabled Action')
|
||||||
|
->setIcon('file'));
|
||||||
|
|
||||||
|
$view->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setUser($user)
|
||||||
|
->setHref($request->getRequestURI())
|
||||||
|
->setRenderAsForm(true)
|
||||||
|
->setName('Form Action')
|
||||||
|
->setIcon('file'));
|
||||||
|
|
||||||
|
$view->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setUser($user)
|
||||||
|
->setHref($request->getRequestURI())
|
||||||
|
->setRenderAsForm(true)
|
||||||
|
->setDisabled(true)
|
||||||
|
->setName('Disabled Form Action')
|
||||||
|
->setIcon('file'));
|
||||||
|
|
||||||
|
$view->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setUser($user)
|
||||||
|
->setHref($request->getRequestURI())
|
||||||
|
->setWorkflow(true)
|
||||||
|
->setName('Workflow Action')
|
||||||
|
->setIcon('file'));
|
||||||
|
|
||||||
|
$view->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setUser($user)
|
||||||
|
->setHref($request->getRequestURI())
|
||||||
|
->setRenderAsForm(true)
|
||||||
|
->setWorkflow(true)
|
||||||
|
->setName('Form + Workflow Action')
|
||||||
|
->setIcon('file'));
|
||||||
|
|
||||||
|
return array(
|
||||||
|
$view,
|
||||||
|
'<div style="clear: both;"></div>',
|
||||||
|
$notices,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,10 +42,6 @@ final class PhabricatorActionListView extends AphrontView {
|
||||||
throw new Exception("Call setUser() before render()!");
|
throw new Exception("Call setUser() before render()!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->object) {
|
|
||||||
throw new Exception("Call setObject() before render()!");
|
|
||||||
}
|
|
||||||
|
|
||||||
$event = new PhabricatorEvent(
|
$event = new PhabricatorEvent(
|
||||||
PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS,
|
PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS,
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -19,10 +19,12 @@
|
||||||
final class PhabricatorActionView extends AphrontView {
|
final class PhabricatorActionView extends AphrontView {
|
||||||
|
|
||||||
private $name;
|
private $name;
|
||||||
|
private $user;
|
||||||
private $icon;
|
private $icon;
|
||||||
private $href;
|
private $href;
|
||||||
private $disabled;
|
private $disabled;
|
||||||
private $workflow;
|
private $workflow;
|
||||||
|
private $renderAsForm;
|
||||||
|
|
||||||
public function setHref($href) {
|
public function setHref($href) {
|
||||||
$this->href = $href;
|
$this->href = $href;
|
||||||
|
@ -49,6 +51,16 @@ final class PhabricatorActionView extends AphrontView {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setRenderAsForm($form) {
|
||||||
|
$this->renderAsForm = $form;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUser(PhabricatorUser $user) {
|
||||||
|
$this->user = $user;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function render() {
|
public function render() {
|
||||||
|
|
||||||
$icon = null;
|
$icon = null;
|
||||||
|
@ -63,14 +75,37 @@ final class PhabricatorActionView extends AphrontView {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->href) {
|
if ($this->href) {
|
||||||
$item = javelin_render_tag(
|
if ($this->renderAsForm) {
|
||||||
'a',
|
if (!$this->user) {
|
||||||
array(
|
throw new Exception(
|
||||||
'href' => $this->href,
|
'Call setUser() when rendering an action as a form.');
|
||||||
'class' => 'phabricator-action-view-item',
|
}
|
||||||
'sigil' => $this->workflow ? 'workflow' : null,
|
|
||||||
),
|
$item = javelin_render_tag(
|
||||||
phutil_escape_html($this->name));
|
'button',
|
||||||
|
array(
|
||||||
|
'class' => 'phabricator-action-view-item',
|
||||||
|
),
|
||||||
|
phutil_escape_html($this->name));
|
||||||
|
|
||||||
|
$item = phabricator_render_form(
|
||||||
|
$this->user,
|
||||||
|
array(
|
||||||
|
'action' => $this->href,
|
||||||
|
'method' => 'POST',
|
||||||
|
'sigil' => $this->workflow ? 'workflow' : null,
|
||||||
|
),
|
||||||
|
$item);
|
||||||
|
} else {
|
||||||
|
$item = javelin_render_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => $this->href,
|
||||||
|
'class' => 'phabricator-action-view-item',
|
||||||
|
'sigil' => $this->workflow ? 'workflow' : null,
|
||||||
|
),
|
||||||
|
phutil_escape_html($this->name));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$item = phutil_render_tag(
|
$item = phutil_render_tag(
|
||||||
'span',
|
'span',
|
||||||
|
|
|
@ -157,7 +157,6 @@ button.link:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
a.toggle {
|
a.toggle {
|
||||||
background: #d3d3d3;
|
background: #d3d3d3;
|
||||||
padding: 2px 6px 3px;
|
padding: 2px 6px 3px;
|
||||||
|
|
|
@ -32,6 +32,21 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.phabricator-action-view button.phabricator-action-view-item {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #3b5998;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-action-view button.phabricator-action-view-item,
|
||||||
.phabricator-action-view-item {
|
.phabricator-action-view-item {
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
padding-left: 34px;
|
padding-left: 34px;
|
||||||
|
@ -53,11 +68,13 @@
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phabricator-action-view-disabled .phabricator-action-view-item {
|
.phabricator-action-view-disabled .phabricator-action-view-item,
|
||||||
|
.phabricator-action-view-disabled button.phabricator-action-view-item {
|
||||||
color: #888888;
|
color: #888888;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phabricator-action-view-disabled .phabricator-action-view-item:hover {
|
.phabricator-action-view-disabled .phabricator-action-view-item:hover,
|
||||||
|
.phabricator-action-view-disabled button.phabricator-action-view-item:hover {
|
||||||
background-color: #dfdfdf;
|
background-color: #dfdfdf;
|
||||||
color: #888888;
|
color: #888888;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue