1
0
Fork 0
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:
epriestley 2012-10-03 10:21:39 -07:00
parent 62d431fd7c
commit 087c328f89
9 changed files with 221 additions and 43 deletions

View file

@ -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',
), ),
)); ));

View file

@ -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',

View file

@ -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) {

View file

@ -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,

View file

@ -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,
);
}
}

View file

@ -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(

View file

@ -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',

View file

@ -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;

View file

@ -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;
} }