mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-24 22:40:55 +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' =>
|
||||
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',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -2325,7 +2325,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'phabricator-core-buttons-css' =>
|
||||
array(
|
||||
'uri' => '/res/a105abeb/rsrc/css/core/buttons.css',
|
||||
'uri' => '/res/427fac91/rsrc/css/core/buttons.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -3003,7 +3003,7 @@ celerity_register_resource_map(array(
|
|||
), array(
|
||||
'packages' =>
|
||||
array(
|
||||
'495635fb' =>
|
||||
'19bef443' =>
|
||||
array(
|
||||
'name' => 'core.pkg.css',
|
||||
'symbols' =>
|
||||
|
@ -3032,7 +3032,7 @@ celerity_register_resource_map(array(
|
|||
21 => 'phabricator-flag-css',
|
||||
22 => 'aphront-error-view-css',
|
||||
),
|
||||
'uri' => '/res/pkg/495635fb/core.pkg.css',
|
||||
'uri' => '/res/pkg/19bef443/core.pkg.css',
|
||||
'type' => 'css',
|
||||
),
|
||||
'3a455e4f' =>
|
||||
|
@ -3199,20 +3199,20 @@ celerity_register_resource_map(array(
|
|||
'reverse' =>
|
||||
array(
|
||||
'aphront-attached-file-view-css' => '7839ae2d',
|
||||
'aphront-crumbs-view-css' => '495635fb',
|
||||
'aphront-dialog-view-css' => '495635fb',
|
||||
'aphront-error-view-css' => '495635fb',
|
||||
'aphront-form-view-css' => '495635fb',
|
||||
'aphront-crumbs-view-css' => '19bef443',
|
||||
'aphront-dialog-view-css' => '19bef443',
|
||||
'aphront-error-view-css' => '19bef443',
|
||||
'aphront-form-view-css' => '19bef443',
|
||||
'aphront-headsup-action-list-view-css' => '2ba14b3d',
|
||||
'aphront-headsup-view-css' => '495635fb',
|
||||
'aphront-list-filter-view-css' => '495635fb',
|
||||
'aphront-pager-view-css' => '495635fb',
|
||||
'aphront-panel-view-css' => '495635fb',
|
||||
'aphront-side-nav-view-css' => '495635fb',
|
||||
'aphront-table-view-css' => '495635fb',
|
||||
'aphront-tokenizer-control-css' => '495635fb',
|
||||
'aphront-tooltip-css' => '495635fb',
|
||||
'aphront-typeahead-control-css' => '495635fb',
|
||||
'aphront-headsup-view-css' => '19bef443',
|
||||
'aphront-list-filter-view-css' => '19bef443',
|
||||
'aphront-pager-view-css' => '19bef443',
|
||||
'aphront-panel-view-css' => '19bef443',
|
||||
'aphront-side-nav-view-css' => '19bef443',
|
||||
'aphront-table-view-css' => '19bef443',
|
||||
'aphront-tokenizer-control-css' => '19bef443',
|
||||
'aphront-tooltip-css' => '19bef443',
|
||||
'aphront-typeahead-control-css' => '19bef443',
|
||||
'differential-changeset-view-css' => '2ba14b3d',
|
||||
'differential-core-view-css' => '2ba14b3d',
|
||||
'differential-inline-comment-editor' => 'd05e3c0f',
|
||||
|
@ -3278,15 +3278,15 @@ celerity_register_resource_map(array(
|
|||
'javelin-workflow' => '3a455e4f',
|
||||
'maniphest-task-summary-css' => '7839ae2d',
|
||||
'maniphest-transaction-detail-css' => '7839ae2d',
|
||||
'phabricator-app-buttons-css' => '495635fb',
|
||||
'phabricator-app-buttons-css' => '19bef443',
|
||||
'phabricator-content-source-view-css' => '2ba14b3d',
|
||||
'phabricator-core-buttons-css' => '495635fb',
|
||||
'phabricator-core-css' => '495635fb',
|
||||
'phabricator-directory-css' => '495635fb',
|
||||
'phabricator-core-buttons-css' => '19bef443',
|
||||
'phabricator-core-css' => '19bef443',
|
||||
'phabricator-directory-css' => '19bef443',
|
||||
'phabricator-drag-and-drop-file-upload' => 'd05e3c0f',
|
||||
'phabricator-dropdown-menu' => '3a455e4f',
|
||||
'phabricator-flag-css' => '495635fb',
|
||||
'phabricator-jump-nav' => '495635fb',
|
||||
'phabricator-flag-css' => '19bef443',
|
||||
'phabricator-jump-nav' => '19bef443',
|
||||
'phabricator-keyboard-shortcut' => '3a455e4f',
|
||||
'phabricator-keyboard-shortcut-manager' => '3a455e4f',
|
||||
'phabricator-menu-item' => '3a455e4f',
|
||||
|
@ -3294,11 +3294,11 @@ celerity_register_resource_map(array(
|
|||
'phabricator-paste-file-upload' => '3a455e4f',
|
||||
'phabricator-prefab' => '3a455e4f',
|
||||
'phabricator-project-tag-css' => '7839ae2d',
|
||||
'phabricator-remarkup-css' => '495635fb',
|
||||
'phabricator-remarkup-css' => '19bef443',
|
||||
'phabricator-shaped-request' => 'd05e3c0f',
|
||||
'phabricator-standard-page-view' => '495635fb',
|
||||
'phabricator-standard-page-view' => '19bef443',
|
||||
'phabricator-tooltip' => '3a455e4f',
|
||||
'phabricator-transaction-view-css' => '495635fb',
|
||||
'syntax-highlighting-css' => '495635fb',
|
||||
'phabricator-transaction-view-css' => '19bef443',
|
||||
'syntax-highlighting-css' => '19bef443',
|
||||
),
|
||||
));
|
||||
|
|
|
@ -545,6 +545,7 @@ phutil_register_library_map(array(
|
|||
'PackageModifyMail' => 'applications/owners/mail/PackageModifyMail.php',
|
||||
'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php',
|
||||
'PhabricatorAccessLog' => 'infrastructure/PhabricatorAccessLog.php',
|
||||
'PhabricatorActionListExample' => 'applications/uiexample/examples/PhabricatorActionListExample.php',
|
||||
'PhabricatorActionListView' => 'view/layout/PhabricatorActionListView.php',
|
||||
'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php',
|
||||
'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php',
|
||||
|
@ -1710,6 +1711,7 @@ phutil_register_library_map(array(
|
|||
'PackageDeleteMail' => 'PackageMail',
|
||||
'PackageModifyMail' => 'PackageMail',
|
||||
'Phabricator404Controller' => 'PhabricatorController',
|
||||
'PhabricatorActionListExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorActionListView' => 'AphrontView',
|
||||
'PhabricatorActionView' => 'AphrontView',
|
||||
'PhabricatorAnchorView' => 'AphrontView',
|
||||
|
|
|
@ -34,6 +34,12 @@ final class PhabricatorFlagsUIEventListener extends PhutilEventListener {
|
|||
$user = $event->getUser();
|
||||
$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());
|
||||
|
||||
if ($flag) {
|
||||
|
|
|
@ -53,6 +53,12 @@ final class PhabricatorUIExampleRenderController extends PhabricatorController {
|
|||
$example = $classes[$selected];
|
||||
$example->setRequest($this->getRequest());
|
||||
|
||||
$result = $example->renderExample();
|
||||
if ($result instanceof AphrontResponse) {
|
||||
// This allows examples to generate dialogs, etc., for demonstration.
|
||||
return $result;
|
||||
}
|
||||
|
||||
$nav->appendChild(
|
||||
'<div class="phabricator-ui-example-header">'.
|
||||
'<h1 class="phabricator-ui-example-name">'.
|
||||
|
@ -64,7 +70,7 @@ final class PhabricatorUIExampleRenderController extends PhabricatorController {
|
|||
'</p>'.
|
||||
'</div>');
|
||||
|
||||
$nav->appendChild($example->renderExample());
|
||||
$nav->appendChild($result);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
$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()!");
|
||||
}
|
||||
|
||||
if (!$this->object) {
|
||||
throw new Exception("Call setObject() before render()!");
|
||||
}
|
||||
|
||||
$event = new PhabricatorEvent(
|
||||
PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS,
|
||||
array(
|
||||
|
|
|
@ -19,10 +19,12 @@
|
|||
final class PhabricatorActionView extends AphrontView {
|
||||
|
||||
private $name;
|
||||
private $user;
|
||||
private $icon;
|
||||
private $href;
|
||||
private $disabled;
|
||||
private $workflow;
|
||||
private $renderAsForm;
|
||||
|
||||
public function setHref($href) {
|
||||
$this->href = $href;
|
||||
|
@ -49,6 +51,16 @@ final class PhabricatorActionView extends AphrontView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setRenderAsForm($form) {
|
||||
$this->renderAsForm = $form;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUser(PhabricatorUser $user) {
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
|
||||
$icon = null;
|
||||
|
@ -63,14 +75,37 @@ final class PhabricatorActionView extends AphrontView {
|
|||
}
|
||||
|
||||
if ($this->href) {
|
||||
$item = javelin_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $this->href,
|
||||
'class' => 'phabricator-action-view-item',
|
||||
'sigil' => $this->workflow ? 'workflow' : null,
|
||||
),
|
||||
phutil_escape_html($this->name));
|
||||
if ($this->renderAsForm) {
|
||||
if (!$this->user) {
|
||||
throw new Exception(
|
||||
'Call setUser() when rendering an action as a form.');
|
||||
}
|
||||
|
||||
$item = javelin_render_tag(
|
||||
'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 {
|
||||
$item = phutil_render_tag(
|
||||
'span',
|
||||
|
|
|
@ -157,7 +157,6 @@ button.link:hover {
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
a.toggle {
|
||||
background: #d3d3d3;
|
||||
padding: 2px 6px 3px;
|
||||
|
|
|
@ -32,6 +32,21 @@
|
|||
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 {
|
||||
line-height: 20px;
|
||||
padding-left: 34px;
|
||||
|
@ -53,11 +68,13 @@
|
|||
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;
|
||||
}
|
||||
|
||||
.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;
|
||||
color: #888888;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue