diff --git a/scripts/celerity/generate_sprites.php b/scripts/celerity/generate_sprites.php index df40173c85..5bf389f9a7 100755 --- a/scripts/celerity/generate_sprites.php +++ b/scripts/celerity/generate_sprites.php @@ -159,9 +159,18 @@ $action_template = id(new PhutilSprite()) ->setSourceSize(16, 16); $action_map = array( - 'file' => 'icon/page_white_text.png', - 'fork' => 'icon/arrow_branch.png', - 'edit' => 'icon/page_white_edit.png', + 'file' => 'icon/page_white_text.png', + 'fork' => 'icon/arrow_branch.png', + 'edit' => 'icon/page_white_edit.png', + 'flag-0' => 'icon/flag-0.png', + 'flag-1' => 'icon/flag-1.png', + 'flag-2' => 'icon/flag-2.png', + 'flag-3' => 'icon/flag-3.png', + 'flag-4' => 'icon/flag-4.png', + 'flag-5' => 'icon/flag-5.png', + 'flag-6' => 'icon/flag-6.png', + 'flag-7' => 'icon/flag-7.png', + 'flag-ghost' => 'icon/flag-ghost.png', ); foreach ($action_map as $icon => $source) { diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index d135a3c882..90056a29f2 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -65,8 +65,8 @@ celerity_register_resource_map(array( ), '/rsrc/image/autosprite.png' => array( - 'hash' => '4a85e6c178f7dbdf1dc4f59f47a5e56b', - 'uri' => '/res/4a85e6c1/rsrc/image/autosprite.png', + 'hash' => 'bd70ca6308d6f80a87a10068a04867f8', + 'uri' => '/res/bd70ca63/rsrc/image/autosprite.png', 'disk' => '/rsrc/image/autosprite.png', 'type' => 'png', ), @@ -671,7 +671,7 @@ celerity_register_resource_map(array( ), 'autosprite-css' => array( - 'uri' => '/res/9ed6c0e6/rsrc/css/autosprite.css', + 'uri' => '/res/114f6e40/rsrc/css/autosprite.css', 'type' => 'css', 'requires' => array( @@ -2265,7 +2265,7 @@ celerity_register_resource_map(array( ), 'phabricator-action-list-view-css' => array( - 'uri' => '/res/f70fbcd4/rsrc/css/layout/phabricator-action-list-view.css', + 'uri' => '/res/1b4eef71/rsrc/css/layout/phabricator-action-list-view.css', 'type' => 'css', 'requires' => array( @@ -2603,7 +2603,7 @@ celerity_register_resource_map(array( ), 'phabricator-property-list-view-css' => array( - 'uri' => '/res/598fccad/rsrc/css/layout/phabricator-property-list-view.css', + 'uri' => '/res/ff5d093d/rsrc/css/layout/phabricator-property-list-view.css', 'type' => 'css', 'requires' => array( diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 411db2cfd3..a3d94388bf 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -743,6 +743,7 @@ phutil_register_library_map(array( 'PhabricatorFlagListController' => 'applications/flag/controller/PhabricatorFlagListController.php', 'PhabricatorFlagListView' => 'applications/flag/view/PhabricatorFlagListView.php', 'PhabricatorFlagQuery' => 'applications/flag/query/PhabricatorFlagQuery.php', + 'PhabricatorFlagsUIEventListener' => 'applications/flag/events/PhabricatorFlagsUIEventListener.php', 'PhabricatorFormExample' => 'applications/uiexample/examples/PhabricatorFormExample.php', 'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/PhabricatorGarbageCollectorDaemon.php', 'PhabricatorGitGraphStream' => 'applications/repository/daemon/PhabricatorGitGraphStream.php', @@ -1864,6 +1865,7 @@ phutil_register_library_map(array( 'PhabricatorFlagEditController' => 'PhabricatorFlagController', 'PhabricatorFlagListController' => 'PhabricatorFlagController', 'PhabricatorFlagListView' => 'AphrontView', + 'PhabricatorFlagsUIEventListener' => 'PhutilEventListener', 'PhabricatorFormExample' => 'PhabricatorUIExample', 'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon', 'PhabricatorGlobalLock' => 'PhutilLock', diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 0420e20128..2bea83af0e 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -89,6 +89,10 @@ abstract class PhabricatorApplication { return null; } + public function getEventListeners() { + return array(); + } + /* -( URI Routing )-------------------------------------------------------- */ diff --git a/src/applications/conduit/method/flag/ConduitAPI_flag_query_Method.php b/src/applications/conduit/method/flag/ConduitAPI_flag_query_Method.php index 9368977f1e..04d4f4e5b4 100644 --- a/src/applications/conduit/method/flag/ConduitAPI_flag_query_Method.php +++ b/src/applications/conduit/method/flag/ConduitAPI_flag_query_Method.php @@ -48,6 +48,7 @@ final class ConduitAPI_flag_query_Method extends ConduitAPI_flag_Method { protected function execute(ConduitAPIRequest $request) { $query = new PhabricatorFlagQuery(); + $query->setViewer($request->getUser()); $owner_phids = $request->getValue('ownerPHIDs', array()); if ($owner_phids) { diff --git a/src/applications/directory/controller/PhabricatorDirectoryMainController.php b/src/applications/directory/controller/PhabricatorDirectoryMainController.php index e0784a211a..e9526f648e 100644 --- a/src/applications/directory/controller/PhabricatorDirectoryMainController.php +++ b/src/applications/directory/controller/PhabricatorDirectoryMainController.php @@ -206,6 +206,7 @@ final class PhabricatorDirectoryMainController $user = $this->getRequest()->getUser(); $flag_query = id(new PhabricatorFlagQuery()) + ->setViewer($user) ->withOwnerPHIDs(array($user->getPHID())) ->needHandles(true) ->setLimit(10); diff --git a/src/applications/flag/application/PhabricatorApplicationFlags.php b/src/applications/flag/application/PhabricatorApplicationFlags.php index 016e992954..e6721ec522 100644 --- a/src/applications/flag/application/PhabricatorApplicationFlags.php +++ b/src/applications/flag/application/PhabricatorApplicationFlags.php @@ -30,6 +30,12 @@ final class PhabricatorApplicationFlags extends PhabricatorApplication { return 'flags'; } + public function getEventListeners() { + return array( + new PhabricatorFlagsUIEventListener(), + ); + } + public function loadStatus(PhabricatorUser $user) { $status = array(); diff --git a/src/applications/flag/controller/PhabricatorFlagEditController.php b/src/applications/flag/controller/PhabricatorFlagEditController.php index ed1f292dcb..16926e0268 100644 --- a/src/applications/flag/controller/PhabricatorFlagEditController.php +++ b/src/applications/flag/controller/PhabricatorFlagEditController.php @@ -29,7 +29,7 @@ final class PhabricatorFlagEditController extends PhabricatorFlagController { $user = $request->getUser(); $phid = $this->phid; - $handle = PhabricatorObjectHandleData::loadOneHandle($phid); + $handle = PhabricatorObjectHandleData::loadOneHandle($phid, $user); if (!$handle->isComplete()) { return new Aphront404Response(); diff --git a/src/applications/flag/controller/PhabricatorFlagListController.php b/src/applications/flag/controller/PhabricatorFlagListController.php index a310e5f73c..da784cb920 100644 --- a/src/applications/flag/controller/PhabricatorFlagListController.php +++ b/src/applications/flag/controller/PhabricatorFlagListController.php @@ -29,6 +29,7 @@ final class PhabricatorFlagListController extends PhabricatorFlagController { $query = new PhabricatorFlagQuery(); $query->withOwnerPHIDs(array($user->getPHID())); + $query->setViewer($user); $query->needHandles(true); $flags = $query->execute(); diff --git a/src/applications/flag/events/PhabricatorFlagsUIEventListener.php b/src/applications/flag/events/PhabricatorFlagsUIEventListener.php new file mode 100644 index 0000000000..9ed31c4b06 --- /dev/null +++ b/src/applications/flag/events/PhabricatorFlagsUIEventListener.php @@ -0,0 +1,64 @@ +listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS); + } + + public function handleEvent(PhutilEvent $event) { + switch ($event->getType()) { + case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS: + $this->handleActionEvent($event); + break; + } + } + + private function handleActionEvent($event) { + $user = $event->getUser(); + $object = $event->getValue('object'); + + $flag = PhabricatorFlagQuery::loadUserFlag($user, $object->getPHID()); + + if ($flag) { + $color = PhabricatorFlagColor::getColorName($flag->getColor()); + $flag_action = id(new PhabricatorActionView()) + ->setWorkflow(true) + ->setHref('/flag/delete/'.$flag->getID().'/') + ->setName(phutil_escape_html('Remove '.$color.' Flag')) + ->setIcon('flag-'.$flag->getColor()); + } else { + $flag_action = id(new PhabricatorActionView()) + ->setWorkflow(true) + ->setHref('/flag/edit/'.$object->getPHID().'/') + ->setName('Flag For Later') + ->setIcon('flag-ghost'); + + if (!$user->isLoggedIn()) { + $flag_action->setDisabled(true); + } + } + + $actions = $event->getValue('actions'); + $actions[] = $flag_action; + $event->setValue('actions', $actions); + } + +} + diff --git a/src/applications/flag/query/PhabricatorFlagQuery.php b/src/applications/flag/query/PhabricatorFlagQuery.php index 78c6aa6b0c..63135af808 100644 --- a/src/applications/flag/query/PhabricatorFlagQuery.php +++ b/src/applications/flag/query/PhabricatorFlagQuery.php @@ -27,6 +27,12 @@ final class PhabricatorFlagQuery { private $needHandles; private $needObjects; + private $viewer; + + public function setViewer($viewer) { + $this->viewer = $viewer; + return $this; + } public function withOwnerPHIDs(array $owner_phids) { $this->ownerPHIDs = $owner_phids; @@ -94,6 +100,9 @@ final class PhabricatorFlagQuery { if ($this->needHandles || $this->needObjects) { $phids = ipull($data, 'objectPHID'); $query = new PhabricatorObjectHandleData($phids); + if ($this->viewer) { + $query->setViewer($this->viewer); + } if ($this->needHandles) { $handles = $query->loadHandles(); diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php index 971840f05c..ddd559ce74 100644 --- a/src/applications/paste/controller/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/PhabricatorPasteViewController.php @@ -99,6 +99,8 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { PhabricatorPolicyCapability::CAN_EDIT); return id(new PhabricatorActionListView()) + ->setUser($user) + ->setObject($paste) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Fork This Paste')) diff --git a/src/applications/phid/handle/PhabricatorObjectHandleData.php b/src/applications/phid/handle/PhabricatorObjectHandleData.php index 52e03a8cbc..3a535d53a7 100644 --- a/src/applications/phid/handle/PhabricatorObjectHandleData.php +++ b/src/applications/phid/handle/PhabricatorObjectHandleData.php @@ -30,8 +30,14 @@ final class PhabricatorObjectHandleData { return $this; } - public static function loadOneHandle($phid) { - $handles = id(new PhabricatorObjectHandleData(array($phid)))->loadHandles(); + public static function loadOneHandle($phid, $viewer = null) { + $query = new PhabricatorObjectHandleData(array($phid)); + + if ($viewer) { + $query->setViewer($viewer); + } + + $handles = $query->loadHandles(); return $handles[$phid]; } diff --git a/src/infrastructure/events/PhabricatorEventEngine.php b/src/infrastructure/events/PhabricatorEventEngine.php index c4d7729ed7..904b61757c 100644 --- a/src/infrastructure/events/PhabricatorEventEngine.php +++ b/src/infrastructure/events/PhabricatorEventEngine.php @@ -31,6 +31,14 @@ final class PhabricatorEventEngine { id(new DarkConsoleEventPluginAPI())->register(); id(new ManiphestEdgeEventListener())->register(); + $applications = PhabricatorApplication::getAllInstalledApplications(); + foreach ($applications as $application) { + $listeners = $application->getEventListeners(); + foreach ($listeners as $listener) { + $listener->register(); + } + } + } } diff --git a/src/infrastructure/events/constant/PhabricatorEventType.php b/src/infrastructure/events/constant/PhabricatorEventType.php index 8eeb1b7f76..5820ada8f6 100644 --- a/src/infrastructure/events/constant/PhabricatorEventType.php +++ b/src/infrastructure/events/constant/PhabricatorEventType.php @@ -38,4 +38,10 @@ final class PhabricatorEventType extends PhutilEventType { const TYPE_TEST_DIDRUNTEST = 'test.didRunTest'; + const TYPE_UI_DIDRENDERACTIONS = 'ui.didRenderActions'; + + const TYPE_UI_WILLRENDEROBJECTS = 'ui.willRenderObjects'; + const TYPE_UI_DDIDRENDEROBJECT = 'ui.didRenderObject'; + const TYPE_UI_DIDRENDEROBJECTS = 'ui.didRenderObjects'; + } diff --git a/src/view/layout/PhabricatorActionListView.php b/src/view/layout/PhabricatorActionListView.php index 5c7a1a9c67..cfc7671715 100644 --- a/src/view/layout/PhabricatorActionListView.php +++ b/src/view/layout/PhabricatorActionListView.php @@ -19,22 +19,51 @@ final class PhabricatorActionListView extends AphrontView { private $actions = array(); + private $object; + private $user; + + public function setObject(PhabricatorLiskDAO $object) { + $this->object = $object; + return $this; + } + + public function setUser(PhabricatorUser $user) { + $this->user = $user; + return $this; + } public function addAction(PhabricatorActionView $view) { $this->actions[] = $view; return $this; } - public function render() { - require_celerity_resource('phabricator-action-list-view-css'); + if (!$this->user) { + 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( + 'object' => $this->object, + 'actions' => $this->actions, + )); + $event->setUser($this->user); + PhutilEventEngine::dispatchEvent($event); + + $actions = $event->getValue('actions'); + + require_celerity_resource('phabricator-action-list-view-css'); return phutil_render_tag( 'ul', array( 'class' => 'phabricator-action-list-view', ), - $this->renderSingleView($this->actions)); + $this->renderSingleView($actions)); } diff --git a/src/view/layout/PhabricatorPropertyListView.php b/src/view/layout/PhabricatorPropertyListView.php index e3abbcfe69..b13d8b4700 100644 --- a/src/view/layout/PhabricatorPropertyListView.php +++ b/src/view/layout/PhabricatorPropertyListView.php @@ -55,7 +55,12 @@ final class PhabricatorPropertyListView extends AphrontView { array( 'class' => 'phabricator-property-list-view', ), - $list); + $list. + // NOTE: We need this (which is basically a "clear: both;" div) to make + // sure the property list is taller than the action list for objects with + // few properties but many actions. Otherwise, the action list may + // obscure the document content. + '
'); } diff --git a/webroot/rsrc/css/autosprite.css b/webroot/rsrc/css/autosprite.css index 4d62d03452..ed5d10a23a 100644 --- a/webroot/rsrc/css/autosprite.css +++ b/webroot/rsrc/css/autosprite.css @@ -262,3 +262,39 @@ .action-edit { background-position: 0px -2555px; } + +.action-flag-0 { + background-position: 0px -2572px; +} + +.action-flag-1 { + background-position: 0px -2589px; +} + +.action-flag-2 { + background-position: 0px -2606px; +} + +.action-flag-3 { + background-position: 0px -2623px; +} + +.action-flag-4 { + background-position: 0px -2640px; +} + +.action-flag-5 { + background-position: 0px -2657px; +} + +.action-flag-6 { + background-position: 0px -2674px; +} + +.action-flag-7 { + background-position: 0px -2691px; +} + +.action-flag-ghost { + background-position: 0px -2708px; +} diff --git a/webroot/rsrc/css/layout/phabricator-action-list-view.css b/webroot/rsrc/css/layout/phabricator-action-list-view.css index 65fb124fd9..1320d4c2c0 100644 --- a/webroot/rsrc/css/layout/phabricator-action-list-view.css +++ b/webroot/rsrc/css/layout/phabricator-action-list-view.css @@ -10,9 +10,9 @@ border: 1px solid #dcdcdc; padding: .5em 0; - position: absolute; + float: right; margin-top: -30px; - right: 1%; + margin-right: 1%; width: 20%; border-radius: 2px; font-size: 12px; diff --git a/webroot/rsrc/css/layout/phabricator-property-list-view.css b/webroot/rsrc/css/layout/phabricator-property-list-view.css index 0abc7f4c2c..5072568b9a 100644 --- a/webroot/rsrc/css/layout/phabricator-property-list-view.css +++ b/webroot/rsrc/css/layout/phabricator-property-list-view.css @@ -7,8 +7,10 @@ border-style: solid; border-width: 1px 0; background-color: #f9f9f9; +} - overflow: hidden; +.phabriator-property-list-view-end { + clear: both; } .device-desktop .phabricator-property-list-view { diff --git a/webroot/rsrc/image/autosprite.png b/webroot/rsrc/image/autosprite.png index ec0e480ec1..4366ac3c2e 100644 Binary files a/webroot/rsrc/image/autosprite.png and b/webroot/rsrc/image/autosprite.png differ