mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 08:42:41 +01:00
Add a Join / Leave button to Projects
Summary: Make it easy to join or leave (well, slightly less easy) a project. Publish join/leave to feed. Fix a couple of membership editor bugs. Test Plan: Joined, left a project. Reviewers: btrahan, jungejason Reviewed By: btrahan CC: aran, epriestley Maniphest Tasks: T681 Differential Revision: https://secure.phabricator.com/D1485
This commit is contained in:
parent
add1ae945d
commit
cb0bb8165d
13 changed files with 224 additions and 7 deletions
|
@ -1483,7 +1483,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'phabricator-profile-header-css' =>
|
||||
array(
|
||||
'uri' => '/res/d39ef6a4/rsrc/css/application/profile/profile-header-view.css',
|
||||
'uri' => '/res/4b1cb23b/rsrc/css/application/profile/profile-header-view.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
|
|
@ -628,6 +628,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectSubproject' => 'applications/project/storage/subproject',
|
||||
'PhabricatorProjectTransaction' => 'applications/project/storage/transaction',
|
||||
'PhabricatorProjectTransactionType' => 'applications/project/constants/transaction',
|
||||
'PhabricatorProjectUpdateController' => 'applications/project/controller/update',
|
||||
'PhabricatorRedirectController' => 'applications/base/controller/redirect',
|
||||
'PhabricatorRefreshCSRFController' => 'applications/auth/controller/refresh',
|
||||
'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential',
|
||||
|
@ -1313,6 +1314,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectSubproject' => 'PhabricatorProjectDAO',
|
||||
'PhabricatorProjectTransaction' => 'PhabricatorProjectDAO',
|
||||
'PhabricatorProjectTransactionType' => 'PhabricatorProjectConstants',
|
||||
'PhabricatorProjectUpdateController' => 'PhabricatorProjectController',
|
||||
'PhabricatorRedirectController' => 'PhabricatorController',
|
||||
'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController',
|
||||
'PhabricatorRemarkupRuleDifferential' => 'PhabricatorRemarkupRuleObjectName',
|
||||
|
|
|
@ -207,6 +207,8 @@ class AphrontDefaultApplicationConfiguration
|
|||
'affiliation/(?P<id>\d+)/$'
|
||||
=> 'PhabricatorProjectAffiliationEditController',
|
||||
'create/$' => 'PhabricatorProjectCreateController',
|
||||
'update/(?P<id>\d+)/(?P<action>[^/]+)/$'
|
||||
=> 'PhabricatorProjectUpdateController',
|
||||
),
|
||||
|
||||
'/r(?P<callsign>[A-Z]+)(?P<commit>[a-z0-9]+)$'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
* 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.
|
||||
|
@ -77,4 +77,12 @@ abstract class PhabricatorFeedStory {
|
|||
return $this->getStoryData()->getEpoch();
|
||||
}
|
||||
|
||||
final protected function renderHandleList(array $phids) {
|
||||
$list = array();
|
||||
foreach ($phids as $phid) {
|
||||
$list[] = '<strong>'.$this->getHandle($phid)->renderLink().'</strong>';
|
||||
}
|
||||
return implode(', ', $list);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,7 +40,9 @@ class PhabricatorFeedStoryProject extends PhabricatorFeedStory {
|
|||
$old = $data->getValue('old');
|
||||
$new = $data->getValue('new');
|
||||
$proj = $this->getHandle($data->getValue('projectPHID'));
|
||||
$auth = $this->getHandle($data->getAuthorPHID());
|
||||
|
||||
$author_phid = $data->getAuthorPHID();
|
||||
$author = $this->getHandle($author_phid);
|
||||
|
||||
switch ($type) {
|
||||
case PhabricatorProjectTransactionType::TYPE_NAME:
|
||||
|
@ -58,11 +60,36 @@ class PhabricatorFeedStoryProject extends PhabricatorFeedStory {
|
|||
'<strong>'.phutil_escape_html($new).'</strong>).';
|
||||
}
|
||||
break;
|
||||
case PhabricatorProjectTransactionType::TYPE_MEMBERS:
|
||||
$add = array_diff($new, $old);
|
||||
$rem = array_diff($old, $new);
|
||||
|
||||
if ((count($add) == 1) && (count($rem) == 0) &&
|
||||
(head($add) == $author_phid)) {
|
||||
$action = 'joined project <strong>'.$proj->renderLink().'</strong>.';
|
||||
} else if ((count($add) == 0) && (count($rem) == 1) &&
|
||||
(head($rem) == $author_phid)) {
|
||||
$action = 'left project <strong>'.$proj->renderLink().'</strong>.';
|
||||
} else if (empty($rem)) {
|
||||
$action = 'added members to project '.
|
||||
'<strong>'.$proj->renderLink().'</strong>: '.
|
||||
$this->renderHandleList($add).'.';
|
||||
} else if (empty($add)) {
|
||||
$action = 'removed members from project '.
|
||||
'<strong>'.$proj->renderLink().'</strong>: '.
|
||||
$this->renderHandleList($rem).'.';
|
||||
} else {
|
||||
$action = 'changed members of project '.
|
||||
'<strong>'.$proj->renderLink().'</strong>, added: '.
|
||||
$this->renderHandleList($add).'; removed: '.
|
||||
$this->renderHandleList($rem).'.';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$action = 'updated project <strong>'.$proj->renderLink().'</strong>';
|
||||
$action = 'updated project <strong>'.$proj->renderLink().'</strong>.';
|
||||
break;
|
||||
}
|
||||
$view->setTitle('<strong>'.$auth->renderLink().'</strong> '.$action);
|
||||
$view->setTitle('<strong>'.$author->renderLink().'</strong> '.$action);
|
||||
$view->setOneLineStory(true);
|
||||
|
||||
return $view;
|
||||
|
|
|
@ -11,6 +11,7 @@ phutil_require_module('phabricator', 'applications/feed/view/story');
|
|||
phutil_require_module('phabricator', 'applications/project/constants/transaction');
|
||||
|
||||
phutil_require_module('phutil', 'markup');
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorFeedStoryProject.php');
|
||||
|
|
|
@ -52,7 +52,7 @@ class PhabricatorProjectProfileController
|
|||
$picture = null;
|
||||
}
|
||||
|
||||
|
||||
$members = mpull($project->loadAffiliations(), null, 'getUserPHID');
|
||||
|
||||
$nav_view = new AphrontSideNavFilterView();
|
||||
$uri = new PhutilURI('/project/view/'.$project->getID().'/');
|
||||
|
@ -114,6 +114,33 @@ class PhabricatorProjectProfileController
|
|||
phutil_utf8_shorten($profile->getBlurb(), 1024));
|
||||
$header->setProfilePicture($picture);
|
||||
|
||||
$action = null;
|
||||
if (empty($members[$user->getPHID()])) {
|
||||
$action = phabricator_render_form(
|
||||
$user,
|
||||
array(
|
||||
'action' => '/project/update/'.$project->getID().'/join/',
|
||||
'method' => 'post',
|
||||
),
|
||||
phutil_render_tag(
|
||||
'button',
|
||||
array(
|
||||
'class' => 'green',
|
||||
),
|
||||
'Join Project'));
|
||||
} else {
|
||||
$action = javelin_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/project/update/'.$project->getID().'/leave/',
|
||||
'sigil' => 'workflow',
|
||||
'class' => 'grey button',
|
||||
),
|
||||
'Leave Project...');
|
||||
}
|
||||
|
||||
$header->addAction($action);
|
||||
|
||||
$header->appendChild($nav_view);
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
|
|
|
@ -18,6 +18,7 @@ phutil_require_module('phabricator', 'applications/project/controller/base');
|
|||
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/project');
|
||||
phutil_require_module('phabricator', 'infrastructure/celerity/api');
|
||||
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
|
||||
phutil_require_module('phabricator', 'view/control/table');
|
||||
phutil_require_module('phabricator', 'view/layout/profileheader');
|
||||
phutil_require_module('phabricator', 'view/layout/sidenavfilter');
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
<?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.
|
||||
*/
|
||||
|
||||
class PhabricatorProjectUpdateController
|
||||
extends PhabricatorProjectController {
|
||||
|
||||
private $id;
|
||||
private $action;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
$this->action = $data['action'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$project = id(new PhabricatorProject())->load($this->id);
|
||||
if (!$project) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$process_action = false;
|
||||
switch ($this->action) {
|
||||
case 'join':
|
||||
$process_action = $request->isFormPost();
|
||||
break;
|
||||
case 'leave':
|
||||
$process_action = $request->isDialogFormPost();
|
||||
break;
|
||||
default:
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$project_uri = '/project/view/'.$project->getID().'/';
|
||||
|
||||
if ($process_action) {
|
||||
$xactions = array();
|
||||
|
||||
switch ($this->action) {
|
||||
case 'join':
|
||||
$affils = $project->loadAffiliations();
|
||||
$affils = mpull($affils, null, 'getUserPHID');
|
||||
if (empty($affils[$user->getPHID()])) {
|
||||
$affils[$user->getPHID()] = true;
|
||||
|
||||
$xaction = new PhabricatorProjectTransaction();
|
||||
$xaction->setTransactionType(
|
||||
PhabricatorProjectTransactionType::TYPE_MEMBERS);
|
||||
$xaction->setNewValue(array_keys($affils));
|
||||
$xactions[] = $xaction;
|
||||
}
|
||||
break;
|
||||
case 'leave':
|
||||
$affils = $project->loadAffiliations();
|
||||
$affils = mpull($affils, null, 'getUserPHID');
|
||||
if (isset($affils[$user->getPHID()])) {
|
||||
unset($affils[$user->getPHID()]);
|
||||
|
||||
$xaction = new PhabricatorProjectTransaction();
|
||||
$xaction->setTransactionType(
|
||||
PhabricatorProjectTransactionType::TYPE_MEMBERS);
|
||||
$xaction->setNewValue(array_keys($affils));
|
||||
$xactions[] = $xaction;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if ($xactions) {
|
||||
$editor = new PhabricatorProjectEditor($project);
|
||||
$editor->setUser($user);
|
||||
$editor->applyTransactions($xactions);
|
||||
}
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($project_uri);
|
||||
}
|
||||
|
||||
$dialog = null;
|
||||
switch ($this->action) {
|
||||
case 'leave':
|
||||
$dialog = new AphrontDialogView();
|
||||
$dialog->setUser($user);
|
||||
$dialog->setTitle('Really leave project?');
|
||||
$dialog->appendChild(
|
||||
'<p>Your tremendous contributions to this project will be sorely '.
|
||||
'missed. Are you sure you want to leave?</p>');
|
||||
$dialog->addCancelButton($project_uri);
|
||||
$dialog->addSubmitButton('Leave Project');
|
||||
break;
|
||||
default:
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
}
|
22
src/applications/project/controller/update/__init__.php
Normal file
22
src/applications/project/controller/update/__init__.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'aphront/response/404');
|
||||
phutil_require_module('phabricator', 'aphront/response/dialog');
|
||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||
phutil_require_module('phabricator', 'applications/project/constants/transaction');
|
||||
phutil_require_module('phabricator', 'applications/project/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/project/editor/project');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/project');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/transaction');
|
||||
phutil_require_module('phabricator', 'view/dialog');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorProjectUpdateController.php');
|
|
@ -134,7 +134,7 @@ final class PhabricatorProjectEditor {
|
|||
|
||||
$old_value = mpull($affils, 'getUserPHID');
|
||||
$old_value = array_values($old_value);
|
||||
$xaction->setOldValue($affils);
|
||||
$xaction->setOldValue($old_value);
|
||||
|
||||
$new_value = $xaction->getNewValue();
|
||||
$new_value = array_filter($new_value);
|
||||
|
@ -174,6 +174,7 @@ final class PhabricatorProjectEditor {
|
|||
foreach ($new as $phid => $ignored) {
|
||||
if (empty($old[$phid])) {
|
||||
$affil = new PhabricatorProjectAffiliation();
|
||||
$affil->setRole('');
|
||||
$affil->setUserPHID($phid);
|
||||
$add[] = $affil;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ final class PhabricatorProfileHeaderView extends AphrontView {
|
|||
protected $profilePicture;
|
||||
protected $profileName;
|
||||
protected $profileDescription;
|
||||
protected $profileActions = array();
|
||||
|
||||
public function setProfilePicture($picture) {
|
||||
$this->profilePicture = $picture;
|
||||
|
@ -37,6 +38,11 @@ final class PhabricatorProfileHeaderView extends AphrontView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function addAction($action) {
|
||||
$this->profileActions[] = $action;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
require_celerity_resource('phabricator-profile-header-css');
|
||||
|
||||
|
@ -57,6 +63,9 @@ final class PhabricatorProfileHeaderView extends AphrontView {
|
|||
<td class="profile-header-name">'.
|
||||
phutil_escape_html($this->profileName).
|
||||
'</td>
|
||||
<td class="profile-header-actions" rowspan="2">'.
|
||||
self::renderSingleView($this->profileActions).
|
||||
'</td>
|
||||
<td class="profile-header-picture" rowspan="2">'.
|
||||
$image.
|
||||
'</td>
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.phabricator-profile-header .profile-header-actions {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.phabricator-profile-header .profile-header-picture-frame {
|
||||
margin: 11px;
|
||||
width: 50px;
|
||||
|
|
Loading…
Reference in a new issue