diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0102a54ed8..e97050fdbc 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => '0b64e988', 'conpherence.pkg.js' => '6249a1cf', 'core.pkg.css' => '85f51b68', - 'core.pkg.js' => '2c684890', + 'core.pkg.js' => '666970d7', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '9535a7e6', 'differential.pkg.js' => 'ddfeb49b', @@ -374,7 +374,7 @@ return array( 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 'rsrc/js/application/aphlict/Aphlict.js' => '5359e785', - 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '2a171a9d', + 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'fb20ac8d', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'edd1ba66', @@ -530,6 +530,7 @@ return array( 'rsrc/js/core/behavior-toggle-class.js' => '92b9ec77', 'rsrc/js/core/behavior-tokenizer.js' => 'b3a4b884', 'rsrc/js/core/behavior-tooltip.js' => '42fcb747', + 'rsrc/js/core/behavior-user-menu.js' => '31420f77', 'rsrc/js/core/behavior-watch-anchor.js' => '9f36c42d', 'rsrc/js/core/behavior-workflow.js' => '0a3f3021', 'rsrc/js/core/phtize.js' => 'd254d646', @@ -595,7 +596,7 @@ return array( 'inline-comment-summary-css' => '51efda3a', 'javelin-aphlict' => '5359e785', 'javelin-behavior' => '61cbc29a', - 'javelin-behavior-aphlict-dropdown' => '2a171a9d', + 'javelin-behavior-aphlict-dropdown' => 'caade6f2', 'javelin-behavior-aphlict-listen' => 'fb20ac8d', 'javelin-behavior-aphlict-status' => '5e2634b9', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', @@ -719,6 +720,7 @@ return array( 'javelin-behavior-toggle-widget' => '3dbf94d5', 'javelin-behavior-typeahead-browse' => '635de1ec', 'javelin-behavior-typeahead-search' => '93d0c9e3', + 'javelin-behavior-user-menu' => '31420f77', 'javelin-behavior-view-placeholder' => '47830651', 'javelin-behavior-workflow' => '0a3f3021', 'javelin-color' => '7e41274a', @@ -1114,17 +1116,6 @@ return array( 'javelin-install', 'javelin-util', ), - '2a171a9d' => array( - 'javelin-behavior', - 'javelin-request', - 'javelin-stratcom', - 'javelin-vector', - 'javelin-dom', - 'javelin-uri', - 'javelin-behavior-device', - 'phabricator-title', - 'phabricator-favicon', - ), '2b8de964' => array( 'javelin-install', 'javelin-util', @@ -1144,6 +1135,9 @@ return array( '2ee659ce' => array( 'javelin-install', ), + '31420f77' => array( + 'javelin-behavior', + ), '320810c8' => array( 'javelin-install', 'javelin-dom', @@ -1999,6 +1993,17 @@ return array( 'javelin-stratcom', 'phabricator-phtize', ), + 'caade6f2' => array( + 'javelin-behavior', + 'javelin-request', + 'javelin-stratcom', + 'javelin-vector', + 'javelin-dom', + 'javelin-uri', + 'javelin-behavior-device', + 'phabricator-title', + 'phabricator-favicon', + ), 'ccf1cbf8' => array( 'javelin-install', 'javelin-dom', diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 2136a3e4b0..e50045b0e9 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -175,7 +175,9 @@ abstract class PhabricatorApplication foreach ($articles as $article) { $item = id(new PhabricatorActionView()) ->setName($article['name']) - ->setHref($article['href']); + ->setHref($article['href']) + ->addSigil('help-item') + ->setOpenInNewWindow(true); $items[] = $item; } } @@ -189,12 +191,21 @@ abstract class PhabricatorApplication $href = '/applications/mailcommands/'.$class.'/'.$key.'/'; $item = id(new PhabricatorActionView()) ->setName($spec['name']) - ->setHref($href); + ->setHref($href) + ->addSigil('help-item') + ->setOpenInNewWindow(true); $items[] = $item; } } - return $items; + if ($items) { + $divider = id(new PhabricatorActionView()) + ->addSigil('help-item') + ->setType(PhabricatorActionView::TYPE_DIVIDER); + array_unshift($items, $divider); + } + + return array_values($items); } public function getHelpDocumentationArticles(PhabricatorUser $viewer) { diff --git a/src/applications/home/application/PhabricatorHomeApplication.php b/src/applications/home/application/PhabricatorHomeApplication.php index 9364879e18..d3823dab85 100644 --- a/src/applications/home/application/PhabricatorHomeApplication.php +++ b/src/applications/home/application/PhabricatorHomeApplication.php @@ -2,8 +2,6 @@ final class PhabricatorHomeApplication extends PhabricatorApplication { - private $application; - const DASHBOARD_DEFAULT = 'dashboard:default'; public function getBaseURI() { @@ -53,25 +51,40 @@ final class PhabricatorHomeApplication extends PhabricatorApplication { } $image = $viewer->getProfileImageURI(); - if ($controller) { - $this->application = $controller->getCurrentApplication(); - } $profile_image = id(new PHUIIconView()) ->setImage($image) ->setHeadSize(PHUIIconView::HEAD_SMALL); + if ($controller) { + $application = $controller->getCurrentApplication(); + } else { + $application = null; + } + $dropdown_menu = $this->renderUserDropdown($viewer, $application); + + $menu_id = celerity_generate_unique_node_id(); + + Javelin::initBehavior( + 'user-menu', + array( + 'menuID' => $menu_id, + 'menu' => $dropdown_menu->getDropdownMenuMetadata(), + )); + return id(new PHUIButtonView()) + ->setID($menu_id) ->setTag('a') ->setHref('/p/'.$viewer->getUsername().'/') ->setIcon($profile_image) ->addClass('phabricator-core-user-menu') - ->setNoCSS(true) - ->setDropdown(true) - ->setDropdownMenu($this->renderUserDropdown($viewer)); + ->setHasCaret(true) + ->setNoCSS(true); } - private function renderUserDropdown(PhabricatorUser $viewer) { + private function renderUserDropdown( + PhabricatorUser $viewer, + $application) { $view = id(new PhabricatorActionListView()) ->setViewer($viewer); @@ -98,16 +111,10 @@ final class PhabricatorHomeApplication extends PhabricatorApplication { ->setHref('/people/manage/'.$viewer->getID().'/')); // Help Menus - if ($this->application) { - $application = $this->application; + if ($application) { $help_links = $application->getHelpMenuItems($viewer); if ($help_links) { - $view->addAction( - id(new PhabricatorActionView()) - ->setType(PhabricatorActionView::TYPE_DIVIDER)); - foreach ($help_links as $link) { - $link->setOpenInNewWindow(true); $view->addAction($link); } } @@ -116,11 +123,13 @@ final class PhabricatorHomeApplication extends PhabricatorApplication { // Logout Menu $view->addAction( id(new PhabricatorActionView()) + ->addSigil('logout-item') ->setType(PhabricatorActionView::TYPE_DIVIDER)); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Log Out %s', $viewer->getUsername())) + ->addSigil('logout-item') ->setHref('/logout/') ->setWorkflow(true)); diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php index 755ed892f0..74e3c0b12d 100644 --- a/src/view/layout/PhabricatorActionView.php +++ b/src/view/layout/PhabricatorActionView.php @@ -278,7 +278,7 @@ final class PhabricatorActionView extends AphrontView { array($icon, $this->name, $caret)); } } else { - $item = phutil_tag( + $item = javelin_tag( 'span', array( 'class' => 'phabricator-action-view-item', diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index 96e43e81fe..67c4dc188c 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -767,22 +767,6 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView ->setViewer($viewer); $dropdown_query->execute(); - $rendered_dropdowns = array(); - $applications = array( - 'PhabricatorHomeApplication', - ); - foreach ($applications as $application_class) { - if (!PhabricatorApplication::isClassInstalledForViewer( - $application_class, - $viewer)) { - continue; - } - $application = PhabricatorApplication::getByClass($application_class); - $menu = $application->buildMainMenuExtraNodes($viewer, $controller); - // TODO: Doesn't work with Quicksand active. - $rendered_dropdowns[$application_class] = hsprintf('%s', $menu); - } - $hisec_warning_config = $this->getHighSecurityWarningConfig(); $console_config = null; @@ -798,6 +782,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView $application_class = null; $application_search_icon = null; + $application_help = null; $controller = $this->getController(); if ($controller) { $application = $controller->getCurrentApplication(); @@ -806,6 +791,16 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView if ($application->getApplicationSearchDocumentTypes()) { $application_search_icon = $application->getIcon(); } + + $help_items = $application->getHelpMenuItems($viewer); + if ($help_items) { + $help_list = id(new PhabricatorActionListView()) + ->setViewer($viewer); + foreach ($help_items as $help_item) { + $help_list->addAction($help_item); + } + $application_help = $help_list->getDropdownMenuMetadata(); + } } } @@ -817,11 +812,11 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView $dropdown_query->getConpherenceData(), ), 'globalDragAndDrop' => $upload_enabled, - 'aphlictDropdowns' => $rendered_dropdowns, 'hisecWarningConfig' => $hisec_warning_config, 'consoleConfig' => $console_config, 'applicationClass' => $application_class, 'applicationSearchIcon' => $application_search_icon, + 'helpItems' => $application_help, ) + $this->buildAphlictListenConfigData(); } diff --git a/src/view/phui/PHUIButtonView.php b/src/view/phui/PHUIButtonView.php index ffe83500d3..c4ee7a190c 100644 --- a/src/view/phui/PHUIButtonView.php +++ b/src/view/phui/PHUIButtonView.php @@ -28,6 +28,7 @@ final class PHUIButtonView extends AphrontTagView { private $name; private $tooltip; private $noCSS; + private $hasCaret; public function setName($name) { $this->name = $name; @@ -93,6 +94,15 @@ final class PHUIButtonView extends AphrontTagView { return $this; } + public function setHasCaret($has_caret) { + $this->hasCaret = $has_caret; + return $this; + } + + public function getHasCaret() { + return $this->hasCaret; + } + public function setIcon($icon, $first = true) { if (!($icon instanceof PHUIIconView)) { $icon = id(new PHUIIconView()) @@ -201,7 +211,7 @@ final class PHUIButtonView extends AphrontTagView { } $caret = null; - if ($this->dropdown) { + if ($this->dropdown || $this->getHasCaret()) { $caret = phutil_tag('span', array('class' => 'caret'), ''); } diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js index f12be7a22b..21db1d94d5 100644 --- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js +++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js @@ -91,24 +91,6 @@ JX.behavior('aphlict-dropdown', function(config, statics) { null, function (e) { var data = e.getData(); - if (config.local && config.applicationClass) { - var local_dropdowns = data.newResponse.aphlictDropdowns; - if (local_dropdowns[config.applicationClass]) { - JX.DOM.replace( - dropdown, - JX.$H(local_dropdowns[config.applicationClass])); - dropdown = JX.$(config.dropdownID); - if (dropdown.childNodes.length === 0) { - JX.DOM.hide(bubble); - } else { - JX.DOM.show(bubble); - } - } else { - JX.DOM.hide(bubble); - } - return; - } - if (!data.fromServer) { return; } diff --git a/webroot/rsrc/js/core/behavior-user-menu.js b/webroot/rsrc/js/core/behavior-user-menu.js new file mode 100644 index 0000000000..b74b9d36ab --- /dev/null +++ b/webroot/rsrc/js/core/behavior-user-menu.js @@ -0,0 +1,57 @@ +/** + * @provides javelin-behavior-user-menu + * @requires javelin-behavior + */ + +JX.behavior('user-menu', function(config) { + var node = JX.$(config.menuID); + var list = JX.$H(config.menu.items).getFragment().firstChild; + + var menu = new JX.PHUIXDropdownMenu(node); + + menu.listen('open', function() { + menu.setContent(list); + }); + + // When the user navigates to a new page, we may need to update the links + // to documentation in the menu. + JX.Stratcom.listen('quicksand-redraw', null, function(e) { + var data = e.getData(); + + var new_help = data.newResponse.helpItems; + var nodes; + if (new_help) { + nodes = JX.$H(new_help.items).getFragment().firstChild.children; + } else { + nodes = []; + } + + var ii; + + var tail = []; + for (ii = list.children.length - 1; ii >= 0; ii--) { + var node = list.children[ii]; + + // Remove any old help items. + if (JX.Stratcom.hasSigil(node.firstChild, 'help-item')) { + JX.DOM.remove(node); + } + + // Place the logout items aside, if any exist. + if (JX.Stratcom.hasSigil(node.firstChild, 'logout-item')) { + JX.DOM.remove(node); + tail.push(node); + } + } + + while (nodes.length) { + list.appendChild(nodes[0]); + } + + tail.reverse(); + for (ii = 0; ii < tail.length; ii++) { + list.appendChild(tail[ii]); + } + }); + +});