diff --git a/scripts/celerity/generate_sprites.php b/scripts/celerity/generate_sprites.php index 32be7420f9..df40173c85 100755 --- a/scripts/celerity/generate_sprites.php +++ b/scripts/celerity/generate_sprites.php @@ -161,6 +161,7 @@ $action_template = id(new PhutilSprite()) $action_map = array( 'file' => 'icon/page_white_text.png', 'fork' => 'icon/arrow_branch.png', + 'edit' => 'icon/page_white_edit.png', ); foreach ($action_map as $icon => $source) { diff --git a/src/applications/paste/application/PhabricatorApplicationPaste.php b/src/applications/paste/application/PhabricatorApplicationPaste.php index 925db87345..0ca94dd5c4 100644 --- a/src/applications/paste/application/PhabricatorApplicationPaste.php +++ b/src/applications/paste/application/PhabricatorApplicationPaste.php @@ -35,6 +35,7 @@ final class PhabricatorApplicationPaste extends PhabricatorApplication { '/P(?P\d+)' => 'PhabricatorPasteViewController', '/paste/' => array( '' => 'PhabricatorPasteEditController', + 'edit/(?P\d+)/' => 'PhabricatorPasteEditController', 'filter/(?P\w+)/' => 'PhabricatorPasteListController', ), ); diff --git a/src/applications/paste/controller/PhabricatorPasteEditController.php b/src/applications/paste/controller/PhabricatorPasteEditController.php index e9d15ad541..27a00b7f9a 100644 --- a/src/applications/paste/controller/PhabricatorPasteEditController.php +++ b/src/applications/paste/controller/PhabricatorPasteEditController.php @@ -18,26 +18,54 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { + private $id; + + public function willProcessRequest(array $data) { + $this->id = idx($data, 'id'); + } + + public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); - $paste = new PhabricatorPaste(); - $title = 'Create Paste'; - - $parent_id = $request->getStr('parent'); $parent = null; - if ($parent_id) { - // NOTE: If the Paste is forked from a paste which the user no longer - // has permission to see, we still let them edit it. - $parent = id(new PhabricatorPasteQuery()) - ->setViewer($user) - ->withIDs(array($parent_id)) - ->execute(); - $parent = head($parent); + $parent_id = null; + if (!$this->id) { + $is_create = true; - if ($parent) { - $paste->setParentPHID($parent->getPHID()); + $paste = new PhabricatorPaste(); + + $parent_id = $request->getStr('parent'); + if ($parent_id) { + // NOTE: If the Paste is forked from a paste which the user no longer + // has permission to see, we still let them edit it. + $parent = id(new PhabricatorPasteQuery()) + ->setViewer($user) + ->withIDs(array($parent_id)) + ->execute(); + $parent = head($parent); + + if ($parent) { + $paste->setParentPHID($parent->getPHID()); + } + } + + $paste->setAuthorPHID($user->getPHID()); + } else { + $is_create = false; + + $paste = id(new PhabricatorPasteQuery()) + ->setViewer($user) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->withIDs(array($this->id)) + ->executeOne(); + if (!$paste) { + return new Aphront404Response(); } } @@ -45,33 +73,36 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { $e_text = true; $errors = array(); if ($request->isFormPost()) { - $text = $request->getStr('text'); - if (!strlen($text)) { - $e_text = 'Required'; - $errors[] = 'The paste may not be blank.'; - } else { - $e_text = null; + + if ($is_create) { + $text = $request->getStr('text'); + if (!strlen($text)) { + $e_text = 'Required'; + $errors[] = 'The paste may not be blank.'; + } else { + $e_text = null; + } } $paste->setTitle($request->getStr('title')); $paste->setLanguage($request->getStr('language')); if (!$errors) { - $paste_file = PhabricatorFile::newFromFileData( - $text, - array( - 'name' => $title, - 'mime-type' => 'text/plain; charset=utf-8', - 'authorPHID' => $user->getPHID(), - )); - $paste->setFilePHID($paste_file->getPHID()); - $paste->setAuthorPHID($user->getPHID()); + if ($is_create) { + $paste_file = PhabricatorFile::newFromFileData( + $text, + array( + 'name' => $paste->getTitle(), + 'mime-type' => 'text/plain; charset=utf-8', + 'authorPHID' => $user->getPHID(), + )); + $paste->setFilePHID($paste_file->getPHID()); + } $paste->save(); - return id(new AphrontRedirectResponse())->setURI($paste->getURI()); } } else { - if ($parent) { + if ($is_create && $parent) { $paste->setTitle('Fork of '.$parent->getFullName()); $paste->setLanguage($parent->getLanguage()); @@ -96,9 +127,6 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { '' => '(Detect With Wizardly Powers)', ) + PhabricatorEnv::getEnvConfig('pygments.dropdown-choices'); - $submit = id(new AphrontFormSubmitControl()) - ->setValue('Create Paste'); - $form ->setUser($user) ->addHiddenInput('parent', $parent_id) @@ -112,15 +140,19 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { ->setLabel('Language') ->setName('language') ->setValue($paste->getLanguage()) - ->setOptions($langs)) - ->appendChild( - id(new AphrontFormTextAreaControl()) - ->setLabel('Text') - ->setError($e_text) - ->setValue($text) - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) - ->setCustomClass('PhabricatorMonospaced') - ->setName('text')) + ->setOptions($langs)); + + if ($is_create) { + $form + ->appendChild( + id(new AphrontFormTextAreaControl()) + ->setLabel('Text') + ->setError($e_text) + ->setValue($text) + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) + ->setCustomClass('PhabricatorMonospaced') + ->setName('text')); + } /* TODO: Doesn't have any useful options yet. ->appendChild( @@ -132,13 +164,25 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { ->setName('policy')) */ + $submit = new AphrontFormSubmitControl(); + + if (!$is_create) { + $submit->addCancelButton($paste->getURI()); + $submit->setValue('Save Paste'); + $title = 'Edit '.$paste->getFullName(); + } else { + $submit->setValue('Create Paste'); + $title = 'Create Paste'; + } + + $form ->appendChild($submit); $nav = $this->buildSideNavView(); $nav->selectFilter('edit'); $nav->appendChild( array( - id(new PhabricatorHeaderView())->setHeader('Create Paste'), + id(new PhabricatorHeaderView())->setHeader($title), $error_view, $form, )); diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php index ce975f83cf..971840f05c 100644 --- a/src/applications/paste/controller/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/PhabricatorPasteViewController.php @@ -59,7 +59,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { $fork_phids)); $header = $this->buildHeaderView($paste); - $actions = $this->buildActionView($paste, $file); + $actions = $this->buildActionView($user, $paste, $file); $properties = $this->buildPropertyView($paste, $fork_phids); $source_code = $this->buildSourceCodeView($paste, $file); @@ -89,9 +89,15 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { } private function buildActionView( + PhabricatorUser $user, PhabricatorPaste $paste, PhabricatorFile $file) { + $can_edit = PhabricatorPolicyFilter::hasCapability( + $user, + $paste, + PhabricatorPolicyCapability::CAN_EDIT); + return id(new PhabricatorActionListView()) ->addAction( id(new PhabricatorActionView()) @@ -102,7 +108,14 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { id(new PhabricatorActionView()) ->setName(pht('View Raw File')) ->setIcon('file') - ->setHref($file->getBestURI())); + ->setHref($file->getBestURI())) + ->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Paste')) + ->setIcon('edit') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($this->getApplicationURI('/edit/'.$paste->getID().'/'))); } private function buildPropertyView( diff --git a/src/applications/paste/storage/PhabricatorPaste.php b/src/applications/paste/storage/PhabricatorPaste.php index b3b73546e4..9064dac2b8 100644 --- a/src/applications/paste/storage/PhabricatorPaste.php +++ b/src/applications/paste/storage/PhabricatorPaste.php @@ -44,11 +44,15 @@ final class PhabricatorPaste extends PhabricatorPasteDAO public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { - return PhabricatorPolicies::POLICY_USER; + if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { + return PhabricatorPolicies::POLICY_USER; + } + return PhabricatorPolicies::POLICY_NOONE; } public function hasAutomaticCapability($capability, PhabricatorUser $user) { diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php index 778e0d4845..73294baad3 100644 --- a/src/view/layout/PhabricatorActionView.php +++ b/src/view/layout/PhabricatorActionView.php @@ -21,6 +21,8 @@ final class PhabricatorActionView extends AphrontView { private $name; private $icon; private $href; + private $disabled; + private $workflow; public function setHref($href) { $this->href = $href; @@ -37,6 +39,16 @@ final class PhabricatorActionView extends AphrontView { return $this; } + public function setDisabled($disabled) { + $this->disabled = $disabled; + return $this; + } + + public function setWorkflow($workflow) { + $this->workflow = $workflow; + return $this; + } + public function render() { $icon = null; @@ -51,11 +63,12 @@ final class PhabricatorActionView extends AphrontView { } if ($this->href) { - $item = phutil_render_tag( + $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 { @@ -67,10 +80,16 @@ final class PhabricatorActionView extends AphrontView { phutil_escape_html($this->name)); } + $classes = array(); + $classes[] = 'phabricator-action-view'; + if ($this->disabled) { + $classes[] = 'phabricator-action-view-disabled'; + } + return phutil_render_tag( 'li', array( - 'class' => 'phabricator-action-view', + 'class' => implode(' ', $classes), ), $icon.$item); } diff --git a/webroot/rsrc/css/autosprite.css b/webroot/rsrc/css/autosprite.css index fa9492afbb..4d62d03452 100644 --- a/webroot/rsrc/css/autosprite.css +++ b/webroot/rsrc/css/autosprite.css @@ -258,3 +258,7 @@ .action-fork { background-position: 0px -2538px; } + +.action-edit { + background-position: 0px -2555px; +} diff --git a/webroot/rsrc/css/layout/phabricator-action-list-view.css b/webroot/rsrc/css/layout/phabricator-action-list-view.css index 37b076dbb8..65fb124fd9 100644 --- a/webroot/rsrc/css/layout/phabricator-action-list-view.css +++ b/webroot/rsrc/css/layout/phabricator-action-list-view.css @@ -52,3 +52,12 @@ color: #ffffff; text-decoration: none; } + +.phabricator-action-view-disabled .phabricator-action-view-item { + color: #888888; +} + +.phabricator-action-view-disabled .phabricator-action-view-item:hover { + background-color: #dfdfdf; + color: #888888; +} diff --git a/webroot/rsrc/image/autosprite.png b/webroot/rsrc/image/autosprite.png index 6730b0097a..ec0e480ec1 100644 Binary files a/webroot/rsrc/image/autosprite.png and b/webroot/rsrc/image/autosprite.png differ