Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php
index 7616344273..b53c8c22ea 100644
--- a/src/applications/base/controller/PhabricatorController.php
+++ b/src/applications/base/controller/PhabricatorController.php
@@ -1,241 +1,250 @@
<?php
abstract class PhabricatorController extends AphrontController {
private $handles;
public function shouldRequireLogin() {
// If this install is configured to allow public resources and the
// controller works in public mode, allow the request through.
$is_public_allowed = PhabricatorEnv::getEnvConfig('policy.allow-public');
if ($is_public_allowed && $this->shouldAllowPublic()) {
return false;
}
return true;
}
public function shouldRequireAdmin() {
return false;
}
public function shouldRequireEnabledUser() {
return true;
}
public function shouldAllowPublic() {
return false;
}
public function shouldRequireEmailVerification() {
$need_verify = PhabricatorUserEmail::isEmailVerificationRequired();
$need_login = $this->shouldRequireLogin();
return ($need_login && $need_verify);
}
final public function willBeginExecution() {
$request = $this->getRequest();
$user = new PhabricatorUser();
$phusr = $request->getCookie('phusr');
$phsid = $request->getCookie('phsid');
if (strlen($phusr) && $phsid) {
$info = queryfx_one(
$user->establishConnection('r'),
'SELECT u.* FROM %T u JOIN %T s ON u.phid = s.userPHID
AND s.type LIKE %> AND s.sessionKey = %s',
$user->getTableName(),
'phabricator_session',
'web-',
$phsid);
if ($info) {
$user->loadFromArray($info);
}
}
$translation = $user->getTranslation();
if ($translation &&
$translation != PhabricatorEnv::getEnvConfig('translation.provider')) {
$translation = newv($translation, array());
PhutilTranslator::getInstance()
->setLanguage($translation->getLanguage())
->addTranslations($translation->getTranslations());
}
$request->setUser($user);
if ($user->getIsDisabled() && $this->shouldRequireEnabledUser()) {
$disabled_user_controller = new PhabricatorDisabledUserController(
$request);
return $this->delegateToController($disabled_user_controller);
}
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_CONTROLLER_CHECKREQUEST,
array(
'request' => $request,
'controller' => get_class($this),
));
$event->setUser($user);
PhutilEventEngine::dispatchEvent($event);
$checker_controller = $event->getValue('controller');
if ($checker_controller != get_class($this)) {
return $this->delegateToController($checker_controller);
}
if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) {
if ($user->getConsoleEnabled() ||
PhabricatorEnv::getEnvConfig('darkconsole.always-on')) {
$console = new DarkConsoleCore();
$request->getApplicationConfiguration()->setConsole($console);
}
}
if ($this->shouldRequireLogin() && !$user->getPHID()) {
$login_controller = new PhabricatorLoginController($request);
return $this->delegateToController($login_controller);
}
if ($this->shouldRequireEmailVerification()) {
$email = $user->loadPrimaryEmail();
if (!$email) {
throw new Exception(
"No primary email address associated with this account!");
}
if (!$email->getIsVerified()) {
$verify_controller = new PhabricatorMustVerifyEmailController($request);
return $this->delegateToController($verify_controller);
}
}
if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) {
return new Aphront403Response();
}
}
public function buildStandardPageView() {
$view = new PhabricatorStandardPageView();
$view->setRequest($this->getRequest());
$view->setController($this);
return $view;
}
public function buildStandardPageResponse($view, array $data) {
$page = $this->buildStandardPageView();
$page->appendChild($view);
$response = new AphrontWebpageResponse();
$response->setContent($page->render());
return $response;
}
public function getApplicationURI($path = '') {
if (!$this->getCurrentApplication()) {
throw new Exception("No application!");
}
return $this->getCurrentApplication()->getBaseURI().ltrim($path, '/');
}
public function buildApplicationPage($view, array $options) {
$page = $this->buildStandardPageView();
$application = $this->getCurrentApplication();
if ($application) {
$page->setApplicationName($application->getName());
$page->setTitle(idx($options, 'title'));
if ($application->getTitleGlyph()) {
$page->setGlyph($application->getTitleGlyph());
}
}
if (!($view instanceof AphrontSideNavFilterView)) {
$nav = new AphrontSideNavFilterView();
$nav->appendChild($view);
$view = $nav;
}
$view->setUser($this->getRequest()->getUser());
$view->setFlexNav(true);
$page->appendChild($view);
if (idx($options, 'device')) {
$page->setDeviceReady(true);
$view->appendChild($page->renderFooter());
}
+ $application_menu = $this->buildApplicationMenu();
+ if ($application_menu) {
+ $page->setApplicationMenu($application_menu);
+ }
+
$response = new AphrontWebpageResponse();
return $response->setContent($page->render());
}
public function didProcessRequest($response) {
$request = $this->getRequest();
$response->setRequest($request);
if ($response instanceof AphrontDialogResponse) {
if (!$request->isAjax()) {
$view = new PhabricatorStandardPageView();
$view->setRequest($request);
$view->setController($this);
$view->appendChild(
'<div style="padding: 2em 0;">'.
$response->buildResponseString().
'</div>');
$response = new AphrontWebpageResponse();
$response->setContent($view->render());
return $response;
} else {
return id(new AphrontAjaxResponse())
->setContent(array(
'dialog' => $response->buildResponseString(),
));
}
} else if ($response instanceof AphrontRedirectResponse) {
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())
->setContent(
array(
'redirect' => $response->getURI(),
));
}
}
return $response;
}
protected function getHandle($phid) {
if (empty($this->handles[$phid])) {
throw new Exception(
"Attempting to access handle which wasn't loaded: {$phid}");
}
return $this->handles[$phid];
}
protected function loadHandles(array $phids) {
$phids = array_filter($phids);
$this->handles = $this->loadViewerHandles($phids);
return $this;
}
protected function getLoadedHandles() {
return $this->handles;
}
protected function loadViewerHandles(array $phids) {
return id(new PhabricatorObjectHandleData($phids))
->setViewer($this->getRequest()->getUser())
->loadHandles();
}
protected function renderHandlesForPHIDs(array $phids) {
$items = array();
foreach ($phids as $phid) {
$items[] = $this->getHandle($phid)->renderLink();
}
return implode('<br />', $items);
}
+ protected function buildApplicationMenu() {
+ return null;
+ }
+
}
diff --git a/src/applications/paste/controller/PhabricatorPasteController.php b/src/applications/paste/controller/PhabricatorPasteController.php
index de0d7345c9..8237f7c694 100644
--- a/src/applications/paste/controller/PhabricatorPasteController.php
+++ b/src/applications/paste/controller/PhabricatorPasteController.php
@@ -1,34 +1,38 @@
<?php
abstract class PhabricatorPasteController extends PhabricatorController {
public function buildSideNavView(PhabricatorPaste $paste = null) {
$user = $this->getRequest()->getUser();
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI('filter/')));
if ($paste) {
$nav->addFilter('paste', 'P'.$paste->getID(), '/P'.$paste->getID());
$nav->addSpacer();
}
$nav->addLabel('Create');
$nav->addFilter(
'edit',
'New Paste',
$this->getApplicationURI(),
$relative = false,
$class = ($user->isLoggedIn() ? null : 'disabled'));
$nav->addSpacer();
$nav->addLabel('Pastes');
if ($user->isLoggedIn()) {
$nav->addFilter('my', 'My Pastes');
}
$nav->addFilter('all', 'All Pastes');
return $nav;
}
+ public function buildApplicationMenu() {
+ return $this->buildSideNavView(null)->getMenu();
+ }
+
}
diff --git a/src/view/layout/AphrontSideNavFilterView.php b/src/view/layout/AphrontSideNavFilterView.php
index f57600923f..0da3ecad0a 100644
--- a/src/view/layout/AphrontSideNavFilterView.php
+++ b/src/view/layout/AphrontSideNavFilterView.php
@@ -1,246 +1,250 @@
<?php
/**
* Like an @{class:AphrontSideNavView}, but with a little bit of logic for the
* common case where you're using the side nav to filter some view of objects.
*
* For example:
*
* $nav = new AphrontSideNavFilterView();
* $nav
* ->setBaseURI($some_uri)
* ->addLabel('Cats')
* ->addFilter('meow', 'Meow')
* ->addFilter('purr', 'Purr')
* ->addSpacer()
* ->addLabel('Dogs')
* ->addFilter('woof', 'Woof')
* ->addFilter('bark', 'Bark');
* $valid_filter = $nav->selectFilter($user_selection, $default = 'meow');
*
*/
final class AphrontSideNavFilterView extends AphrontView {
private $items = array();
private $baseURI;
private $selectedFilter = false;
private $flexNav;
private $flexible;
private $user;
private $active;
private $menu;
public function __construct() {
- $this->menu = id(new PhabricatorMenuView());
+ $this->menu = new PhabricatorMenuView();
}
public function setActive($active) {
$this->active = $active;
return $this;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function setFlexNav($flex_nav) {
$this->flexNav = $flex_nav;
return $this;
}
public function setFlexible($flexible) {
$this->flexible = $flexible;
return $this;
}
public function addMenuItem(PhabricatorMenuItemView $item) {
$this->menu->addMenuItem($item);
return $this;
}
+ public function getMenu() {
+ return $this->menu;
+ }
+
public function addFilter(
$key,
$name,
$uri = null) {
$item = id(new PhabricatorMenuItemView())
->setKey($key)
->setName($name);
if ($uri) {
$item->setHref($uri);
} else {
$href = clone $this->baseURI;
$href->setPath(rtrim($href->getPath().$key, '/').'/');
$href = (string)$href;
$item->setHref($href);
}
return $this->addMenuItem($item);
}
public function addCustomBlock($block) {
$this->menu->appendChild($block);
return $this;
}
public function addLabel($name) {
return $this->addMenuItem(
id(new PhabricatorMenuItemView())
->setType(PhabricatorMenuItemView::TYPE_LABEL)
->setName($name));
}
public function addSpacer() {
return $this->addMenuItem(
id(new PhabricatorMenuItemView())
->setType(PhabricatorMenuItemView::TYPE_SPACER));
}
public function setBaseURI(PhutilURI $uri) {
$this->baseURI = $uri;
return $this;
}
public function getBaseURI() {
return $this->baseURI;
}
public function selectFilter($key, $default = null) {
$this->selectedFilter = $default;
if ($this->menu->getItem($key)) {
$this->selectedFilter = $key;
}
return $this->selectedFilter;
}
public function render() {
if ($this->menu->getItems()) {
if (!$this->baseURI) {
throw new Exception("Call setBaseURI() before render()!");
}
if ($this->selectedFilter === false) {
throw new Exception("Call selectFilter() before render()!");
}
}
$selected_item = $this->menu->getItem($this->selectedFilter);
if ($selected_item) {
$selected_item->addClass('phabricator-menu-item-selected');
}
require_celerity_resource('phabricator-side-menu-view-css');
if ($this->flexNav) {
return $this->renderFlexNav();
} else {
return $this->renderLegacyNav();
}
}
private function renderFlexNav() {
$user = $this->user;
require_celerity_resource('phabricator-nav-view-css');
$nav_classes = array();
$nav_classes[] = 'phabricator-nav';
$nav_id = null;
$drag_id = null;
$content_id = celerity_generate_unique_node_id();
$local_id = null;
$local_menu = null;
$main_id = celerity_generate_unique_node_id();
if ($this->flexible) {
$drag_id = celerity_generate_unique_node_id();
$flex_bar = phutil_render_tag(
'div',
array(
'class' => 'phabricator-nav-drag',
'id' => $drag_id,
),
'');
} else {
$flex_bar = null;
}
$nav_menu = null;
if ($this->menu->getItems()) {
$local_id = celerity_generate_unique_node_id();
$nav_classes[] = 'has-local-nav';
$local_menu = phutil_render_tag(
'div',
array(
'class' => 'phabricator-nav-col phabricator-nav-local '.
'phabricator-side-menu',
'id' => $local_id,
),
self::renderSingleView($this->menu));
}
Javelin::initBehavior(
'phabricator-nav',
array(
'mainID' => $main_id,
'localID' => $local_id,
'dragID' => $drag_id,
'contentID' => $content_id,
));
if ($this->active && $local_id) {
Javelin::initBehavior(
'phabricator-active-nav',
array(
'localID' => $local_id,
));
}
$header_part =
'<div class="phabricator-nav-head">'.
'<div class="phabricator-nav-head-tablet">'.
'<a href="#" class="nav-button nav-button-w nav-button-menu" '.
'id="tablet-menu1"></a>'.
'<a href="#" class="nav-button nav-button-e nav-button-content '.
'nav-button-selected" id="tablet-menu2"></a>'.
'</div>'.
'</div>';
return $header_part.phutil_render_tag(
'div',
array(
'class' => implode(' ', $nav_classes),
'id' => $main_id,
),
$local_menu.
$flex_bar.
phutil_render_tag(
'div',
array(
'class' => 'phabricator-nav-content',
'id' => $content_id,
),
$this->renderChildren()));
}
public function renderLegacyNav() {
require_celerity_resource('aphront-side-nav-view-css');
return
'<table class="aphront-side-nav-view phabricator-side-menu">'.
'<tr>'.
'<th class="aphront-side-nav-navigation">'.
self::renderSingleView($this->menu).
'</th>'.
'<td class="aphront-side-nav-content">'.
$this->renderChildren().
'</td>'.
'</tr>'.
'</table>';
}
}
diff --git a/src/view/layout/PhabricatorMenuView.php b/src/view/layout/PhabricatorMenuView.php
index fbafbdb23c..d2517e60be 100644
--- a/src/view/layout/PhabricatorMenuView.php
+++ b/src/view/layout/PhabricatorMenuView.php
@@ -1,50 +1,71 @@
<?php
final class PhabricatorMenuView extends AphrontView {
private $items = array();
private $map = array();
private $classes = array();
public function addClass($class) {
$this->classes[] = $class;
return $this;
}
+ public function newLabel($name) {
+ $item = id(new PhabricatorMenuItemView())
+ ->setType(PhabricatorMenuItemView::TYPE_LABEL)
+ ->setName($name);
+
+ $this->addMenuItem($item);
+
+ return $item;
+ }
+
+ public function newLink($name, $href) {
+ $item = id(new PhabricatorMenuItemView())
+ ->setType(PhabricatorMenuItemView::TYPE_LINK)
+ ->setName($name)
+ ->setHref($href);
+
+ $this->addMenuItem($item);
+
+ return $item;
+ }
+
public function addMenuItem(PhabricatorMenuItemView $item) {
$key = $item->getKey();
if ($key !== null) {
if (isset($this->map[$key])) {
throw new Exception(
"Menu contains duplicate items with key '{$key}'!");
}
$this->map[$key] = $item;
}
$this->items[] = $item;
$this->appendChild($item);
return $this;
}
public function getItem($key) {
return idx($this->map, $key);
}
public function getItems() {
return $this->items;
}
public function render() {
$classes = $this->classes;
$classes[] = 'phabricator-menu-view';
return phutil_render_tag(
'div',
array(
'class' => implode(' ', $classes),
),
$this->renderChildren());
}
}
diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php
index 70e9c8bb22..062a9a8bfb 100644
--- a/src/view/page/PhabricatorStandardPageView.php
+++ b/src/view/page/PhabricatorStandardPageView.php
@@ -1,415 +1,430 @@
<?php
/**
* This is a standard Phabricator page with menus, Javelin, DarkConsole, and
* basic styles.
*
*/
final class PhabricatorStandardPageView extends PhabricatorBarePageView {
private $baseURI;
private $applicationName;
private $glyph;
private $menuContent;
private $showChrome = true;
private $disableConsole;
private $searchDefaultScope;
private $pageObjects = array();
+ private $applicationMenu;
+
+ public function setApplicationMenu(PhabricatorMenuView $application_menu) {
+ $this->applicationMenu = $application_menu;
+ return $this;
+ }
+
+ public function getApplicationMenu() {
+ return $this->applicationMenu;
+ }
public function setApplicationName($application_name) {
$this->applicationName = $application_name;
return $this;
}
public function setDisableConsole($disable) {
$this->disableConsole = $disable;
return $this;
}
public function getApplicationName() {
return $this->applicationName;
}
public function setBaseURI($base_uri) {
$this->baseURI = $base_uri;
return $this;
}
public function getBaseURI() {
return $this->baseURI;
}
public function setShowChrome($show_chrome) {
$this->showChrome = $show_chrome;
return $this;
}
public function getShowChrome() {
return $this->showChrome;
}
public function setSearchDefaultScope($search_default_scope) {
$this->searchDefaultScope = $search_default_scope;
return $this;
}
public function getSearchDefaultScope() {
return $this->searchDefaultScope;
}
public function appendPageObjects(array $objs) {
foreach ($objs as $obj) {
$this->pageObjects[] = $obj;
}
}
public function getTitle() {
$use_glyph = true;
$request = $this->getRequest();
if ($request) {
$user = $request->getUser();
if ($user && $user->loadPreferences()->getPreference(
PhabricatorUserPreferences::PREFERENCE_TITLES) !== 'glyph') {
$use_glyph = false;
}
}
return ($use_glyph ?
$this->getGlyph() : '['.$this->getApplicationName().']').
' '.parent::getTitle();
}
protected function willRenderPage() {
parent::willRenderPage();
if (!$this->getRequest()) {
throw new Exception(
"You must set the Request to render a PhabricatorStandardPageView.");
}
$console = $this->getConsole();
require_celerity_resource('phabricator-core-css');
require_celerity_resource('autosprite-css');
require_celerity_resource('phabricator-core-buttons-css');
require_celerity_resource('phabricator-standard-page-view');
Javelin::initBehavior('workflow', array());
$current_token = null;
$request = $this->getRequest();
if ($request) {
$user = $request->getUser();
if ($user) {
$current_token = $user->getCSRFToken();
$download_form = phabricator_render_form_magic($user);
$default_img_uri =
PhabricatorEnv::getCDNURI(
'/rsrc/image/icon/fatcow/document_black.png'
);
Javelin::initBehavior(
'lightbox-attachments',
array(
'defaultImageUri' => $default_img_uri,
'downloadForm' => $download_form,
));
}
}
Javelin::initBehavior('toggle-class', array());
Javelin::initBehavior('konami', array());
Javelin::initBehavior(
'refresh-csrf',
array(
'tokenName' => AphrontRequest::getCSRFTokenName(),
'header' => AphrontRequest::getCSRFHeaderName(),
'current' => $current_token,
));
Javelin::initBehavior('device', array('id' => 'base-page'));
if ($console) {
require_celerity_resource('aphront-dark-console-css');
Javelin::initBehavior(
'dark-console',
array(
'uri' => '/~/',
'request_uri' => $request ? (string) $request->getRequestURI() : '/',
));
// Change this to initBehavior when there is some behavior to initialize
require_celerity_resource('javelin-behavior-error-log');
}
- $this->menuContent = id(new PhabricatorMainMenuView())
+ $menu = id(new PhabricatorMainMenuView())
->setUser($request->getUser())
->setController($this->getController())
- ->setDefaultSearchScope($this->getSearchDefaultScope())
- ->render();
+ ->setDefaultSearchScope($this->getSearchDefaultScope());
+
+ if ($this->getApplicationMenu()) {
+ $menu->setApplicationMenu($this->getApplicationMenu());
+ }
+
+ $this->menuContent = $menu->render();
}
protected function getHead() {
$monospaced = PhabricatorEnv::getEnvConfig('style.monospace');
$request = $this->getRequest();
if ($request) {
$user = $request->getUser();
if ($user) {
$monospaced = nonempty(
$user->loadPreferences()->getPreference(
PhabricatorUserPreferences::PREFERENCE_MONOSPACED),
$monospaced);
}
}
$response = CelerityAPI::getStaticResourceResponse();
$head = array(
parent::getHead(),
'<style type="text/css">'.
'.PhabricatorMonospaced { font: '.$monospaced.'; }'.
'</style>',
$response->renderSingleResource('javelin-magical-init'),
);
return implode("\n", $head);
}
public function setGlyph($glyph) {
$this->glyph = $glyph;
return $this;
}
public function getGlyph() {
return $this->glyph;
}
protected function willSendResponse($response) {
$response = parent::willSendResponse($response);
$console = $this->getRequest()->getApplicationConfiguration()->getConsole();
if ($console) {
$response = str_replace(
'<darkconsole />',
$console->render($this->getRequest()),
$response);
}
return $response;
}
protected function getBody() {
$console = $this->getConsole();
$login_stuff = null;
$request = $this->getRequest();
$user = null;
if ($request) {
$user = $request->getUser();
// NOTE: user may not be set here if we caught an exception early
// in the execution workflow.
if ($user && $user->getPHID()) {
$login_stuff =
phutil_render_tag(
'a',
array(
'href' => '/p/'.$user->getUsername().'/',
),
phutil_escape_html($user->getUsername())).
' &middot; '.
'<a href="/settings/">Settings</a>'.
' &middot; '.
phabricator_render_form(
$user,
array(
'action' => '/search/',
'method' => 'post',
'style' => 'display: inline',
),
'<div class="menu-section menu-section-search">'.
'<div class="menu-search-container">'.
'<input type="text" name="query" id="standard-search-box" />'.
'<button id="standard-search-button">Search</button>'.
'</div>'.
'</div>'.
' in '.
AphrontFormSelectControl::renderSelectTag(
$this->getSearchDefaultScope(),
PhabricatorSearchScope::getScopeOptions(),
array(
'name' => 'scope',
)).
' '.
'<button>Search</button>');
}
}
$header_chrome = null;
$footer_chrome = null;
if ($this->getShowChrome()) {
$header_chrome = $this->menuContent;
if (!$this->getDeviceReady()) {
$footer_chrome = $this->renderFooter();
}
}
$developer_warning = null;
if (PhabricatorEnv::getEnvConfig('phabricator.show-error-callout') &&
DarkConsoleErrorLogPluginAPI::getErrors()) {
$developer_warning =
'<div class="aphront-developer-error-callout">'.
'This page raised PHP errors. Find them in DarkConsole '.
'or the error log.'.
'</div>';
}
$agent = idx($_SERVER, 'HTTP_USER_AGENT');
// Try to guess the device resolution based on UA strings to avoid a flash
// of incorrectly-styled content.
$device_guess = 'device-desktop';
if (preg_match('/iPhone|iPod/', $agent)) {
$device_guess = 'device-phone';
} else if (preg_match('/iPad/', $agent)) {
$device_guess = 'device-tablet';
}
$classes = array(
'phabricator-standard-page',
$device_guess,
);
$classes = implode(' ', $classes);
return
phutil_render_tag(
'div',
array(
'id' => 'base-page',
'class' => $classes,
),
$header_chrome.
'<div class="phabricator-standard-page-body">'.
($console ? '<darkconsole />' : null).
$developer_warning.
parent::getBody().
'<div style="clear: both;"></div>'.
'</div>').
$footer_chrome;
}
protected function getTail() {
$request = $this->getRequest();
$user = $request->getUser();
$container = null;
if (PhabricatorEnv::getEnvConfig('notification.enabled') &&
$user->isLoggedIn()) {
$aphlict_object_id = celerity_generate_unique_node_id();
$aphlict_container_id = celerity_generate_unique_node_id();
$client_uri = PhabricatorEnv::getEnvConfig('notification.client-uri');
$client_uri = new PhutilURI($client_uri);
if ($client_uri->getDomain() == 'localhost') {
$this_host = $this->getRequest()->getHost();
$this_host = new PhutilURI('http://'.$this_host.'/');
$client_uri->setDomain($this_host->getDomain());
}
$enable_debug = PhabricatorEnv::getEnvConfig('notification.debug');
Javelin::initBehavior(
'aphlict-listen',
array(
'id' => $aphlict_object_id,
'containerID' => $aphlict_container_id,
'server' => $client_uri->getDomain(),
'port' => $client_uri->getPort(),
'debug' => $enable_debug,
'pageObjects' => array_fill_keys($this->pageObjects, true),
));
$container = phutil_render_tag(
'div',
array(
'id' => $aphlict_container_id,
'style' => 'position: absolute; width: 0; height: 0;',
),
'');
}
$response = CelerityAPI::getStaticResourceResponse();
$tail = array(
parent::getTail(),
$container,
$response->renderHTMLFooter(),
);
return implode("\n", $tail);
}
protected function getBodyClasses() {
$classes = array();
if (!$this->getShowChrome()) {
$classes[] = 'phabricator-chromeless-page';
}
return implode(' ', $classes);
}
private function getConsole() {
if ($this->disableConsole) {
return null;
}
return $this->getRequest()->getApplicationConfiguration()->getConsole();
}
public function renderFooter() {
$console = $this->getConsole();
$foot_links = array();
$version = PhabricatorEnv::getEnvConfig('phabricator.version');
$foot_links[] =
'<a href="http://phabricator.org/">Phabricator</a> '.
phutil_escape_html($version);
$foot_links[] =
'<a href="https://secure.phabricator.com/maniphest/task/create/">'.
'Report a Bug'.
'</a>';
if (PhabricatorEnv::getEnvConfig('darkconsole.enabled') &&
!PhabricatorEnv::getEnvConfig('darkconsole.always-on')) {
if ($console) {
$link = javelin_render_tag(
'a',
array(
'href' => '/~/',
'sigil' => 'workflow',
),
'Disable DarkConsole');
} else {
$link = javelin_render_tag(
'a',
array(
'href' => '/~/',
'sigil' => 'workflow',
),
'Enable DarkConsole');
}
$foot_links[] = $link;
}
$foot_links = implode(' &middot; ', $foot_links);
return
'<div class="phabricator-page-foot">'.
$foot_links.
'</div>';
}
}
diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php
index 131ad377c5..a833960e4d 100644
--- a/src/view/page/menu/PhabricatorMainMenuView.php
+++ b/src/view/page/menu/PhabricatorMainMenuView.php
@@ -1,303 +1,357 @@
<?php
final class PhabricatorMainMenuView extends AphrontView {
private $user;
private $defaultSearchScope;
private $controller;
+ private $applicationMenu;
+
+
+ public function setApplicationMenu(PhabricatorMenuView $application_menu) {
+ $this->applicationMenu = $application_menu;
+ return $this;
+ }
+
+ public function getApplicationMenu() {
+ return $this->applicationMenu;
+ }
public function setController(PhabricatorController $controller) {
$this->controller = $controller;
return $this;
}
public function getController() {
return $this->controller;
}
public function setDefaultSearchScope($default_search_scope) {
$this->defaultSearchScope = $default_search_scope;
return $this;
}
public function getDefaultSearchScope() {
return $this->defaultSearchScope;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function getUser() {
return $this->user;
}
public function render() {
$user = $this->user;
require_celerity_resource('phabricator-main-menu-view');
$header_id = celerity_generate_unique_node_id();
$menus = array();
$group = new PhabricatorMainMenuGroupView();
$group->addClass('phabricator-main-menu-group-logo');
$group->setCollapsible(false);
$group->appendChild(
phutil_render_tag(
'a',
array(
'class' => 'phabricator-main-menu-logo',
'href' => '/',
),
''));
if (PhabricatorEnv::getEnvConfig('notification.enabled') &&
$user->isLoggedIn()) {
list($menu, $dropdown) = $this->renderNotificationMenu();
$group->appendChild($menu);
$menus[] = $dropdown;
}
$group->appendChild(
javelin_render_tag(
'a',
array(
'class' => 'phabricator-main-menu-expand-button',
'sigil' => 'jx-toggle-class',
'meta' => array(
'map' => array(
$header_id => 'phabricator-core-menu-expand',
),
),
),
''));
$logo = $group->render();
$phabricator_menu = $this->renderPhabricatorMenu();
// $menus[] = $this->renderApplicationMenu();
$actions = '';
+ $application_menu = $this->getApplicationMenu();
+ if ($application_menu) {
+ $application_menu->addClass('phabricator-dark-menu');
+ $application_menu->addClass('phabricator-application-menu');
+ }
+
return phutil_render_tag(
'div',
array(
'class' => 'phabricator-main-menu',
'id' => $header_id,
),
self::renderSingleView(
array(
- $logo,
+ $this->renderPhabricatorMenuButton($header_id),
+ $this->renderApplicationMenuButton($header_id),
+ $this->renderPhabricatorLogo(),
+ $alerts,
$phabricator_menu,
+ $application_menu,
))).
self::renderSingleView($menus);
}
private function renderSearch() {
$user = $this->user;
$result = null;
$keyboard_config = array(
'helpURI' => '/help/keyboardshortcut/',
);
if ($user->isLoggedIn()) {
$search = new PhabricatorMainMenuSearchView();
$search->setUser($user);
$search->setScope($this->getDefaultSearchScope());
$result = $search;
$pref_shortcut = PhabricatorUserPreferences::PREFERENCE_SEARCH_SHORTCUT;
if ($user->loadPreferences()->getPreference($pref_shortcut, true)) {
$keyboard_config['searchID'] = $search->getID();
}
}
Javelin::initBehavior('phabricator-keyboard-shortcuts', $keyboard_config);
if ($result) {
$result = id(new PhabricatorMenuItemView())
->addClass('phabricator-main-menu-search')
->appendChild($result);
}
return $result;
}
+ private function renderPhabricatorMenuButton($header_id) {
+ return javelin_render_tag(
+ 'a',
+ array(
+ 'class' => 'phabricator-main-menu-expand-button '.
+ 'phabricator-expand-core-menu',
+ 'sigil' => 'jx-toggle-class',
+ 'meta' => array(
+ 'map' => array(
+ $header_id => 'phabricator-core-menu-expanded',
+ ),
+ ),
+ ),
+ '');
+ }
+
+ public function renderApplicationMenuButton($header_id) {
+ return javelin_render_tag(
+ 'a',
+ array(
+ 'class' => 'phabricator-main-menu-expand-button '.
+ 'phabricator-expand-application-menu',
+ 'sigil' => 'jx-toggle-class',
+ 'meta' => array(
+ 'map' => array(
+ $header_id => 'phabricator-application-menu-expanded',
+ ),
+ ),
+ ),
+ '');
+ }
+
private function renderPhabricatorMenu() {
$user = $this->getUser();
$controller = $this->getController();
$applications = PhabricatorApplication::getAllInstalledApplications();
$applications = msort($applications, 'getName');
$core = array();
$more = array();
$actions = array();
$group_core = PhabricatorApplication::GROUP_CORE;
foreach ($applications as $application) {
if ($application->shouldAppearInLaunchView()) {
$item = id(new PhabricatorMenuItemView())
->setName($application->getName())
->setHref($application->getBaseURI());
if ($application->getApplicationGroup() == $group_core) {
$core[] = $item;
} else {
$more[] = $item;
}
}
$app_actions = $application->buildMainMenuItems($user, $controller);
foreach ($app_actions as $action) {
$actions[] = $action;
}
}
$view = new PhabricatorMenuView();
+ $view->addClass('phabricator-dark-menu');
$view->addClass('phabricator-core-menu');
$search = $this->renderSearch();
$view->appendChild($search);
if ($core) {
$view->addMenuItem(
id(new PhabricatorMenuItemView())
->addClass('phabricator-core-item-device')
->setType(PhabricatorMenuItemView::TYPE_LABEL)
->setName(pht('Core Applications')));
foreach ($core as $item) {
$item->addClass('phabricator-core-item-device');
$view->addMenuItem($item);
}
}
if ($actions) {
$actions = msort($actions, 'getSortOrder');
$view->addMenuItem(
id(new PhabricatorMenuItemView())
->addClass('phabricator-core-item-device')
->setType(PhabricatorMenuItemView::TYPE_LABEL)
->setName(pht('Actions')));
foreach ($actions as $action) {
$icon = $action->getIcon();
if ($icon) {
$classes = array(
'phabricator-core-menu-icon',
'autosprite',
);
if ($action->getSelected()) {
$classes[] = 'main-menu-item-icon-'.$icon.'-selected';
} else {
$classes[] = 'main-menu-item-icon-'.$icon;
}
$action->appendChild(
phutil_render_tag(
'span',
array(
'class' => implode(' ', $classes),
),
''));
}
$view->addMenuItem($action);
}
}
if ($more) {
$view->addMenuItem(
id(new PhabricatorMenuItemView())
->addClass('phabricator-core-item-device')
->setType(PhabricatorMenuItemView::TYPE_LABEL)
->setName(pht('More Applications')));
foreach ($more as $item) {
$item->addClass('phabricator-core-item-device');
$view->addMenuItem($item);
}
}
return $view;
}
private function renderNotificationMenu() {
$user = $this->user;
require_celerity_resource('phabricator-notification-css');
require_celerity_resource('phabricator-notification-menu-css');
require_celerity_resource('sprite-menu-css');
$count_id = celerity_generate_unique_node_id();
$dropdown_id = celerity_generate_unique_node_id();
$bubble_id = celerity_generate_unique_node_id();
$count_number = id(new PhabricatorFeedStoryNotification())
->countUnread($user);
if ($count_number > 999) {
$count_number = "\xE2\x88\x9E";
}
$count_tag = phutil_render_tag(
'span',
array(
'id' => $count_id,
'class' => 'phabricator-main-menu-alert-count'
),
phutil_escape_html($count_number));
$icon_tag = phutil_render_tag(
'span',
array(
'class' => 'sprite-menu phabricator-main-menu-alert-icon',
),
'');
$container_classes = array(
'phabricator-main-menu-alert-bubble',
'sprite-menu',
'alert-notifications',
);
if ($count_number) {
$container_classes[] = 'alert-unread';
}
$bubble_tag = phutil_render_tag(
'a',
array(
'href' => '/notification/',
'class' => implode(' ', $container_classes),
'id' => $bubble_id,
),
$icon_tag.$count_tag);
Javelin::initBehavior(
'aphlict-dropdown',
array(
'bubbleID' => $bubble_id,
'countID' => $count_id,
'dropdownID' => $dropdown_id,
));
$notification_dropdown = javelin_render_tag(
'div',
array(
'id' => $dropdown_id,
'class' => 'phabricator-notification-menu',
'sigil' => 'phabricator-notification-menu',
'style' => 'display: none;',
),
'');
return array($bubble_tag, $notification_dropdown);
}
}
diff --git a/webroot/rsrc/css/application/base/main-menu-view.css b/webroot/rsrc/css/application/base/main-menu-view.css
index b701454b14..2e7de7a188 100644
--- a/webroot/rsrc/css/application/base/main-menu-view.css
+++ b/webroot/rsrc/css/application/base/main-menu-view.css
@@ -1,361 +1,420 @@
/**
* @provides phabricator-main-menu-view
*/
/* - Main Menu -----------------------------------------------------------------
Main menu at the top of every page that has chrome. It reacts to resolution
changes in order to behave reasonably on tablets and phones.
*/
.phabricator-main-menu {
background: #2d3236;
background-image: url(/rsrc/image/main_texture.png);
background-repeat: repeat-x;
position: relative;
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.25);
z-index: 6;
min-height: 44px;
}
.phabricator-main-menu a:hover {
text-decoration: none;
}
/* - Logo ----------------------------------------------------------------------
The "Phabricator" logo group in the main menu. On tablet and phone devices,
this shows a "reveal" button to expand/collapse the rest of the menu.
*/
.device-desktop .phabricator-main-menu-group-logo {
float: left;
}
.phabricator-main-menu-logo {
display: inline-block;
height: 44px;
width: 170px;
margin-right: 12px;
background: 6px 9px url(/rsrc/image/header_logo.png) no-repeat;
}
/* - Expand/Collapse Button ----------------------------------------------------
On phones, the menu switches to a vertical layout and uses a button to expand
or collapse the items.
*/
.phabricator-main-menu-expand-button {
position: absolute;
- right: 10px;
- top: 10px;
+ top: 7px;
display: block;
width: 40px;
height: 28px;
text-align: center;
background: #22292d url(/rsrc/image/lines.png) no-repeat 8px 6px;
border-radius: 6px;
border: 1px solid #111111;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1),
0 1px 0 rgba(255, 255, 255, 0.075);
}
+.phabricator-expand-core-menu {
+ left: 10px;
+}
+
+.phabricator-expand-application-menu {
+ right: 10px;
+}
+
.device-desktop .phabricator-main-menu-expand-button {
display: none;
}
.device-tablet .phabricator-main-menu-expand-button,
.device-phone .phabricator-main-menu-expand-button {
display: block;
}
-.phabricator-core-menu-expand .phabricator-main-menu-expand-button {
+.phabricator-core-menu-expanded .phabricator-expand-core-menu,
+.phabricator-application-menu-expanded .phabricator-expand-application-menu {
background-color: #55595d;
}
+
+
/* - Search --------------------------------------------------------------------
The main search input in the menu bar.
*/
.device-desktop .phabricator-main-menu-search {
width: 220px;
}
.phabricator-main-menu-search-container {
padding: 10px 0;
position: relative;
height: 24px;
}
.phabricator-main-menu-search-target {
position: absolute;
top: 46px;
}
.device-desktop .phabricator-main-menu-search-target {
width: 320px;
margin-left: -150px;
}
.device-tablet .phabricator-main-menu-search-target,
.device-phone .phabricator-main-menu-search-target {
width: 100%;
margin-left: -25px;
}
.device-desktop .phabricator-main-menu-search-container {
margin: 0 8px 0 50px;
}
.device-tablet .phabricator-main-menu-search-container,
.device-phone .phabricator-main-menu-search-container {
margin: 0 18px 0 60px;
}
.phabricator-main-menu-search input {
outline: 0;
margin: 0;
width: 100%;
right: 0;
position: absolute;
border: 1px solid #333333;
border-radius: 12px;
background: #555555;
height: 12px;
line-height: 12px;
box-shadow: 0px 1px 1px rgba(128, 128, 128, 0.25);
padding: 6px 32px 6px 10px;
}
.phabricator-main-menu-search input:focus {
background: #c9c9c9;
}
.phabricator-main-menu-search input.jx-typeahead-placeholder {
color: #999999;
}
.phabricator-main-menu-search button {
position: absolute;
color: transparent;
background: transparent 5px 6px url(/rsrc/image/search.png) no-repeat;
border: none;
outline: none;
box-shadow: none;
text-shadow: none;
min-width: 0;
height: 20px;
width: 20px;
top: 11px;
right: 6px;
}
.phabricator-main-menu-search-target div.jx-typeahead-results {
border-radius: 4px;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.35);
border: 1px solid #33393d;
}
.phabricator-main-menu-search-target div.jx-typeahead-results a.jx-result {
border: 0;
}
.phabricator-main-menu-search-target div.jx-typeahead-results a.focused,
.phabricator-main-menu-search-target div.jx-typeahead-results a:hover {
background: #3875d7;
}
.phabricator-main-search-typeahead-result {
display: block;
padding: 4px 4px 4px 38px;
background-position: 4px 4px;
background-size: 25px 25px;
background-repeat: no-repeat;
}
.phabricator-main-search-typeahead-result .result-name {
display: block;
font-weight: bold;
color: #444444;
}
.focused .phabricator-main-search-typeahead-result .result-name,
a:hover .phabricator-main-search-typeahead-result .result-name {
color: #eeeeee;
}
.phabricator-main-search-typeahead-result .result-type {
color: #888888;
}
.focused .phabricator-main-search-typeahead-result .result-type,
a:hover .phabricator-main-search-typeahead-result .result-type {
color: #dddddd;
}
/* - Alert ---------------------------------------------------------------------
Alert menus are like icon menus but don't obey collapse rules.
*/
.phabricator-main-menu-alert-bubble {
display: inline-block;
position: relative;
width: 26px;
height: 26px;
margin-bottom: 9px;
}
.phabricator-main-menu-alert-bubble.alert-unread {
width: 46px;
}
.phabricator-main-menu-alert-icon {
position: absolute;
width: 14px;
height: 14px;
top: 4px;
left: 5px;
}
.phabricator-main-menu-alert-count {
position: absolute;
font-weight: bold;
line-height: 22px;
top: 1px;
right: 8px;
left: 20px;
color: #ffffff;
text-align: center;
display: none;
}
.phabricator-main-menu-alert-item {
width: 26px;
height: 26px;
margin: 9px;
display: block;
}
.alert-unread .phabricator-main-menu-alert-count {
display: block;
}
+/* - Dark Menu -----------------------------------------------------------------
+
+ Styles shared between the "core" menu (left button on mobile) and
+ "application" menu (right button on mobile). These styles give the menu a
+ white-on-black appearance.
+
+*/
+
+.device-phone .phabricator-dark-menu,
+.device-tablet .phabricator-dark-menu,
+.device-phone .phabricator-dark-menu a.phabricator-menu-item-type-link,
+.device-tablet .phabricator-dark-menu a.phabricator-menu-item-type-link {
+ color: #ffffff;
+}
+
+.device-phone .phabricator-dark-menu .phabricator-menu-item-view,
+.device-tablet .phabricator-dark-menu .phabricator-menu-item-view {
+ display: block;
+ padding: 4px 0;
+ border-width: 1px 0;
+ border-style: solid;
+ border-color: #35383b transparent #282b2d;
+}
+
+.device-phone .phabricator-dark-menu .phabricator-menu-item-type-label,
+.device-tablet .phabricator-dark-menu .phabricator-menu-item-type-label {
+ text-transform: uppercase;
+ font-size: 11px;
+ background: #151719;
+ padding-left: 12px;
+}
+
+.device-phone .phabricator-dark-menu .phabricator-menu-item-type-spacer,
+.device-tablet .phabricator-dark-menu .phabricator-menu-item-type-spacer {
+ display: none;
+}
+
+
/* - Core Menu -----------------------------------------------------------------
+ Styles unique to the core menu (left button on mobile).
+
*/
.phabricator-core-menu-icon {
position: absolute;
display: block;
width: 26px;
height: 26px;
}
.phabricator-core-menu-profile-image {
background-size: 26px 26px;
}
.device-phone .phabricator-core-menu,
.device-tablet .phabricator-core-menu {
display: none;
}
-.device-phone .phabricator-core-menu-expand .phabricator-core-menu,
-.device-tablet .phabricator-core-menu-expand .phabricator-core-menu {
- display: block;
-}
-
-.device-phone .phabricator-core-menu,
-.device-tablet .phabricator-core-menu,
-.device-phone .phabricator-core-menu a.phabricator-menu-item-type-link,
-.device-tablet .phabricator-core-menu a.phabricator-menu-item-type-link {
- color: #ffffff;
-}
-
-.device-phone .phabricator-core-menu .phabricator-menu-item-view,
-.device-tablet .phabricator-core-menu .phabricator-menu-item-view {
+.device-phone .phabricator-core-menu-expanded .phabricator-core-menu,
+.device-tablet .phabricator-core-menu-expanded .phabricator-core-menu {
display: block;
- padding: 4px 0;
- border-width: 1px 0;
- border-style: solid;
- border-color: #35383b transparent #282b2d;
-}
-
-.device-phone .phabricator-core-menu .phabricator-menu-item-type-label,
-.device-tablet .phabricator-core-menu .phabricator-menu-item-type-label {
- text-transform: uppercase;
- font-size: 11px;
- background: #151719;
- padding-left: 12px;
}
.device-phone .phabricator-core-menu .phabricator-menu-item-type-link,
.device-tablet .phabricator-core-menu .phabricator-menu-item-type-link {
font-size: 15px;
min-height: 30px;
}
.device-phone .phabricator-core-menu
.phabricator-menu-item-type-link .phabricator-menu-item-name,
.device-tablet .phabricator-core-menu
.phabricator-menu-item-type-link .phabricator-menu-item-name {
margin-left: 40px;
line-height: 26px;
}
.device-desktop .phabricator-core-menu {
position: absolute;
right: 0;
top: 0;
vertical-align: top;
}
.device-desktop .phabricator-core-menu .phabricator-menu-item-name {
display: none;
}
.device-desktop .phabricator-core-menu .phabricator-menu-item-view {
display: block;
float: left;
position: relative;
min-width: 38px;
height: 44px;
}
.device-desktop .phabricator-core-menu .phabricator-core-item-device {
display: none;
}
.device-desktop .phabricator-core-menu-icon {
top: 9px;
left: 6px;
}
.device-phone .phabricator-core-menu-icon,
.device-tablet .phabricator-core-menu-icon {
left: 6px;
}
.device-desktop .phabricator-core-menu .phabricator-core-menu-item-profile {
border-width: 0 1px;
border-style: solid;
border-color: #44494d;
margin: 0 8px;
}
+
+
+/* - Application Menu ----------------------------------------------------------
+
+ Styles unique to the application menu (right button on mobile).
+
+*/
+
+.device-phone .phabricator-application-menu-expanded
+ .phabricator-application-menu,
+.device-tablet .phabricator-application-menu-expanded
+ .phabricator-application-menu {
+ display: block;
+ padding-top: 44px;
+}
+
+.phabricator-application-menu {
+ display: none;
+}
+
+.phabricator-application-menu .phabricator-menu-item-type-link
+ .phabricator-menu-item-name {
+ padding-left: 12px;
+}
+
+.device-phone .phabricator-application-menu-expanded
+ .phabricator-application-menu,
+.device-tablet .phabricator-application-menu-expanded
+ .phabricator-application-menu {
+ display: block;
+ padding-top: 44px;
+}
+

File Metadata

Mime Type
text/x-diff
Expires
Mon, Jul 28, 9:07 AM (1 w, 21 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
186633
Default Alt Text
(50 KB)

Event Timeline