From e8cebb7da5a497076c8ce42c4119ed869df9b2ab Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 May 2014 07:18:18 -0700 Subject: [PATCH] Add support for aural-only and visual-only elements Summary: Ref T4843. This adds support to `javelin_tag()` for an `aural` attribute. When specified, `true` values mean "this content is aural-only", while `false` values mean "this content is not aural". - I've attempted to find the best modern approaches for marking this content, but the `aural` attribute should let us change the mechanism later. - Make the "beta" markers on application navigation visual only (see T4843). This information is of very low importance, the application navigation is accessed frequently, and the information is available on the application list. - Partially convert the main navigation. This is mostly to test things, since I want to get more concrete feedback about approaches here. - Add a `?__aural__=1` attribute, which renders the page with aural-only elements visible and visual-only elements colored. Test Plan: {F146476} Reviewers: btrahan, scp, chad Reviewed By: chad Subscribers: aklapper, qgil, epriestley Maniphest Tasks: T4843 Differential Revision: https://secure.phabricator.com/D8830 --- resources/celerity/map.php | 10 +-- .../PhabricatorApplicationAuth.php | 2 + .../PhabricatorApplicationHelp.php | 5 +- .../PhabricatorApplicationHome.php | 1 + .../controller/PhabricatorHomeController.php | 2 +- .../view/PhabricatorApplicationLaunchView.php | 3 +- .../PhabricatorApplicationPeople.php | 1 + .../PhabricatorApplicationSettings.php | 1 + src/infrastructure/javelin/markup.php | 14 ++++ .../control/PhabricatorRemarkupControl.php | 11 ++- src/view/page/PhabricatorStandardPageView.php | 4 + .../page/menu/PhabricatorMainMenuView.php | 73 ++++++++++++++++--- src/view/phui/PHUIListItemView.php | 34 ++++++++- webroot/rsrc/css/core/core.css | 26 +++++++ webroot/rsrc/css/core/z-index.css | 4 + 15 files changed, 171 insertions(+), 20 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index ddfe6e316a..38848c1084 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ return array( 'names' => array( - 'core.pkg.css' => 'b7ba02ba', + 'core.pkg.css' => 'cc50ddf5', 'core.pkg.js' => '417722ff', 'darkconsole.pkg.js' => 'ca8671ce', 'differential.pkg.css' => '8a064eb7', @@ -105,10 +105,10 @@ return array( 'rsrc/css/application/subscriptions/subscribers-list.css' => '5bb30c78', 'rsrc/css/application/tokens/tokens.css' => '5f7bca25', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => 'da26ddb2', + 'rsrc/css/core/core.css' => '7dff07c3', 'rsrc/css/core/remarkup.css' => '98a7627b', 'rsrc/css/core/syntax.css' => '3c18c1cb', - 'rsrc/css/core/z-index.css' => '0d89d53c', + 'rsrc/css/core/z-index.css' => '7e4989ed', 'rsrc/css/diviner/diviner-shared.css' => '38813222', 'rsrc/css/font/font-awesome.css' => '62bc244d', 'rsrc/css/font/font-glyphicons-halflings.css' => 'c4c1c6b6', @@ -686,7 +686,7 @@ return array( 'phabricator-busy' => '6453c869', 'phabricator-chatlog-css' => '852140ff', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => 'da26ddb2', + 'phabricator-core-css' => '7dff07c3', 'phabricator-countdown-css' => '86b7b0a0', 'phabricator-crumbs-view-css' => '0222cbe0', 'phabricator-drag-and-drop-file-upload' => 'ae6abfba', @@ -735,7 +735,7 @@ return array( 'phabricator-uiexample-reactor-select' => '189e4fe3', 'phabricator-uiexample-reactor-sendclass' => 'bf97561d', 'phabricator-uiexample-reactor-sendproperties' => '551add57', - 'phabricator-zindex-css' => '0d89d53c', + 'phabricator-zindex-css' => '7e4989ed', 'phame-css' => '19ecc703', 'pholio-css' => '2fa97dbe', 'pholio-edit-css' => 'b9e59b6d', diff --git a/src/applications/auth/application/PhabricatorApplicationAuth.php b/src/applications/auth/application/PhabricatorApplicationAuth.php index ec05fa594b..0230fe35c1 100644 --- a/src/applications/auth/application/PhabricatorApplicationAuth.php +++ b/src/applications/auth/application/PhabricatorApplicationAuth.php @@ -40,6 +40,7 @@ final class PhabricatorApplicationAuth extends PhabricatorApplication { ->setWorkflow(true) ->setHref('/logout/') ->setSelected(($controller instanceof PhabricatorLogoutController)) + ->setAural(pht('Log Out')) ->setOrder(900); $items[] = $item; } else { @@ -53,6 +54,7 @@ final class PhabricatorApplicationAuth extends PhabricatorApplication { // TODO: Login icon? ->setIcon('power') ->setHref('/auth/start/') + ->setAural(pht('Log In')) ->setOrder(900); $items[] = $item; } diff --git a/src/applications/help/application/PhabricatorApplicationHelp.php b/src/applications/help/application/PhabricatorApplicationHelp.php index 7ba399cdbf..4994d19e69 100644 --- a/src/applications/help/application/PhabricatorApplicationHelp.php +++ b/src/applications/help/application/PhabricatorApplicationHelp.php @@ -31,10 +31,13 @@ final class PhabricatorApplicationHelp extends PhabricatorApplication { } if ($application && $application->getHelpURI()) { + $help_name = pht('%s Help', $application->getName()); + $item = id(new PHUIListItemView()) - ->setName(pht('%s Help', $application->getName())) + ->setName($help_name) ->addClass('core-menu-item') ->setIcon('info-sm') + ->setAural($help_name) ->setOrder(200) ->setHref($application->getHelpURI()); $items[] = $item; diff --git a/src/applications/home/application/PhabricatorApplicationHome.php b/src/applications/home/application/PhabricatorApplicationHome.php index 34ab9db4a9..b2cee3b24e 100644 --- a/src/applications/home/application/PhabricatorApplicationHome.php +++ b/src/applications/home/application/PhabricatorApplicationHome.php @@ -60,6 +60,7 @@ final class PhabricatorApplicationHome extends PhabricatorApplication { ->setHref('/home/create/') ->addSigil('quick-create-menu') ->setID($create_id) + ->setAural(pht('Quick Create')) ->setOrder(300); $items[] = $item; } diff --git a/src/applications/home/controller/PhabricatorHomeController.php b/src/applications/home/controller/PhabricatorHomeController.php index d302d8d7e8..561c673fa8 100644 --- a/src/applications/home/controller/PhabricatorHomeController.php +++ b/src/applications/home/controller/PhabricatorHomeController.php @@ -167,7 +167,7 @@ abstract class PhabricatorHomeController extends PhabricatorController { } } - $nav->addFilter( + $nav->addFilter( '', pht('Customize Applications...'), '/settings/panel/home/'); diff --git a/src/applications/meta/view/PhabricatorApplicationLaunchView.php b/src/applications/meta/view/PhabricatorApplicationLaunchView.php index ed27d64d5f..d40e9b482c 100644 --- a/src/applications/meta/view/PhabricatorApplicationLaunchView.php +++ b/src/applications/meta/view/PhabricatorApplicationLaunchView.php @@ -39,9 +39,10 @@ final class PhabricatorApplicationLaunchView extends AphrontView { $application->getName()); if ($application->isBeta()) { - $content[] = phutil_tag( + $content[] = javelin_tag( 'span', array( + 'aural' => false, 'class' => 'phabricator-application-beta', ), "\xCE\xB2"); diff --git a/src/applications/people/application/PhabricatorApplicationPeople.php b/src/applications/people/application/PhabricatorApplicationPeople.php index f3042dc58e..5fc8e7a049 100644 --- a/src/applications/people/application/PhabricatorApplicationPeople.php +++ b/src/applications/people/application/PhabricatorApplicationPeople.php @@ -120,6 +120,7 @@ final class PhabricatorApplicationPeople extends PhabricatorApplication { ->setName($user->getUsername()) ->setHref('/p/'.$user->getUsername().'/') ->addClass('core-menu-item') + ->setAural(pht('Profile')) ->setOrder(100); $classes = array( diff --git a/src/applications/settings/application/PhabricatorApplicationSettings.php b/src/applications/settings/application/PhabricatorApplicationSettings.php index 8e4aa91e89..65ad9505ad 100644 --- a/src/applications/settings/application/PhabricatorApplicationSettings.php +++ b/src/applications/settings/application/PhabricatorApplicationSettings.php @@ -46,6 +46,7 @@ final class PhabricatorApplicationSettings extends PhabricatorApplication { ->addClass('core-menu-item') ->setSelected($selected) ->setHref('/settings/') + ->setAural(pht('Settings')) ->setOrder(400); $items[] = $item; } diff --git a/src/infrastructure/javelin/markup.php b/src/infrastructure/javelin/markup.php index a15dc097c7..c78de8b86a 100644 --- a/src/infrastructure/javelin/markup.php +++ b/src/infrastructure/javelin/markup.php @@ -36,6 +36,20 @@ function javelin_tag( } } + if (isset($attributes['aural'])) { + if ($attributes['aural']) { + $class = idx($attributes, 'class', ''); + $class = rtrim('aural-only '.$class); + $attributes['class'] = $class; + } else { + $class = idx($attributes, 'class', ''); + $class = rtrim('visual-only '.$class); + $attributes['class'] = $class; + $attributes['aria-hidden'] = 'true'; + } + unset($attributes['aural']); + } + return phutil_tag($tag, $attributes, $content); } diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php index e19f109316..b4dc6fb09e 100644 --- a/src/view/form/control/PhabricatorRemarkupControl.php +++ b/src/view/form/control/PhabricatorRemarkupControl.php @@ -133,13 +133,22 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { $target = '_blank'; } + $content = null; + $tip = idx($spec, 'tip'); if ($tip) { $meta['tip'] = $tip; + $content = phutil_tag( + 'span', + array( + 'class' => 'aural-only', + ), + $tip); } require_celerity_resource('sprite-icons-css'); + $buttons[] = javelin_tag( 'a', array( @@ -156,7 +165,7 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { array( 'class' => 'remarkup-assist sprite-icons remarkup-assist-'.$action, ), - '')); + $content)); } $buttons = phutil_tag( diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index ae32cf8250..fa78759406 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -430,6 +430,10 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView { $classes[] = 'printable'; } + if ($this->getRequest()->getStr('__aural__')) { + $classes[] = 'audible'; + } + return implode(' ', $classes); } diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index 5585a1fa13..383c536a01 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -34,9 +34,10 @@ final class PhabricatorMainMenuView extends AphrontView { $alerts = array(); $search_button = ''; $app_button = ''; + $aural = null; if ($user->isLoggedIn() && $user->isUserActivated()) { - list($menu, $dropdowns) = $this->renderNotificationMenu(); + list($menu, $dropdowns, $aural) = $this->renderNotificationMenu(); $alerts[] = $menu; $menus = array_merge($menus, $dropdowns); $app_button = $this->renderApplicationMenuButton($header_id); @@ -51,14 +52,24 @@ final class PhabricatorMainMenuView extends AphrontView { $search_menu = $this->renderPhabricatorSearchMenu(); if ($alerts) { - $alerts = phutil_tag( + $alerts = javelin_tag( 'div', array( 'class' => 'phabricator-main-menu-alerts', + 'aural' => false, ), $alerts); } + if ($aural) { + $aural = javelin_tag( + 'span', + array( + 'aural' => true, + ), + phutil_implode_html(' ', $aural)); + } + $application_menu = $this->renderApplicationMenu(); $classes = array(); $classes[] = 'phabricator-main-menu'; @@ -76,6 +87,7 @@ final class PhabricatorMainMenuView extends AphrontView { $search_button, $this->renderPhabricatorLogo(), $alerts, + $aural, $application_menu, $search_menu, $menus, @@ -236,12 +248,20 @@ final class PhabricatorMainMenuView extends AphrontView { 'class' => 'phabricator-main-menu-logo', 'href' => '/', ), - phutil_tag( - 'span', - array( - 'class' => 'sprite-menu menu-logo-image '.$class, - ), - '')); + array( + javelin_tag( + 'span', + array( + 'aural' => true, + ), + pht('Home')), + phutil_tag( + 'span', + array( + 'class' => 'sprite-menu menu-logo-image '.$class, + ), + ''), + )); } private function renderNotificationMenu() { @@ -256,6 +276,8 @@ final class PhabricatorMainMenuView extends AphrontView { 'alert-notifications', ); + $aural = array(); + $message_tag = ''; $message_notification_dropdown = ''; $conpherence = 'PhabricatorApplicationConpherence'; @@ -270,6 +292,20 @@ final class PhabricatorMainMenuView extends AphrontView { ->withParticipationStatus($unread_status) ->execute(); $message_count_number = idx($unread, $user->getPHID(), 0); + + if ($message_count_number) { + $aural[] = phutil_tag( + 'a', + array( + 'href' => '/conpherence/', + ), + pht( + '%s unread messages.', + new PhutilNumber($message_count_number))); + } else { + $aural[] = pht('No messages.'); + } + if ($message_count_number > 999) { $message_count_number = "\xE2\x88\x9E"; } @@ -333,6 +369,19 @@ final class PhabricatorMainMenuView extends AphrontView { $count_number = id(new PhabricatorFeedStoryNotification()) ->countUnread($user); + if ($count_number) { + $aural[] = phutil_tag( + 'a', + array( + 'href' => '/notification/', + ), + pht( + '%s unread notifications.', + new PhutilNumber($count_number))); + } else { + $aural[] = pht('No notifications.'); + } + if ($count_number > 999) { $count_number = "\xE2\x88\x9E"; } @@ -397,8 +446,12 @@ final class PhabricatorMainMenuView extends AphrontView { } return array( - hsprintf('%s%s', $bubble_tag, $message_tag), - $dropdowns + array( + $bubble_tag, + $message_tag, + ), + $dropdowns, + $aural, ); } diff --git a/src/view/phui/PHUIListItemView.php b/src/view/phui/PHUIListItemView.php index ef8b8da880..5360771e3c 100644 --- a/src/view/phui/PHUIListItemView.php +++ b/src/view/phui/PHUIListItemView.php @@ -25,6 +25,16 @@ final class PHUIListItemView extends AphrontTagView { private $renderNameAsTooltip; private $statusColor; private $order; + private $aural; + + public function setAural($aural) { + $this->aural = $aural; + return $this; + } + + public function getAural() { + return $this->aural; + } public function setOrder($order) { $this->order = $order; @@ -170,10 +180,21 @@ final class PHUIListItemView extends AphrontTagView { $external = " \xE2\x86\x97"; } + // If this element has an aural representation, make any name visual + // only. This is primarily dealing with the links in the main menu like + // "Profile" and "Logout". If we don't hide the name, the mobile + // version of these elements will have two redundant names. + + $classes = array(); + $classes[] = 'phui-list-item-name'; + if ($this->aural !== null) { + $classes[] = 'visual-only'; + } + $name = phutil_tag( 'span', array( - 'class' => 'phui-list-item-name', + 'class' => implode(' ', $classes), ), array( $this->name, @@ -182,6 +203,16 @@ final class PHUIListItemView extends AphrontTagView { } } + $aural = null; + if ($this->aural !== null) { + $aural = phutil_tag( + 'span', + array( + 'class' => 'aural-only', + ), + $this->aural); + } + if ($this->icon) { $icon_name = $this->icon; if ($this->getDisabled()) { @@ -210,6 +241,7 @@ final class PHUIListItemView extends AphrontTagView { 'sigil' => $sigil, ), array( + $aural, $icon, $this->renderChildren(), $name, diff --git a/webroot/rsrc/css/core/core.css b/webroot/rsrc/css/core/core.css index 812577da55..58f75c1486 100644 --- a/webroot/rsrc/css/core/core.css +++ b/webroot/rsrc/css/core/core.css @@ -123,3 +123,29 @@ hr { background: #bbbbbb; border: none; } + +.aural-only { + position: absolute !important; + clip: rect(1px, 1px, 1px, 1px); +} + +.visual-only { + /* These elements are hidden by the 'aria-hidden' attribute. */ +} + +.audible .aural-only { + clip: auto; + background: #006699; + color: #ffffff; +} + +.audible .aural-only a { + color: #ffffff; + font-weight: bold; +} + +.audible .visual-only { + position: absolute !important; + background: #990066; + opacity: 0.25; +} diff --git a/webroot/rsrc/css/core/z-index.css b/webroot/rsrc/css/core/z-index.css index 3c69073831..6f5ce7ab33 100644 --- a/webroot/rsrc/css/core/z-index.css +++ b/webroot/rsrc/css/core/z-index.css @@ -148,3 +148,7 @@ div.jx-typeahead-results { .jx-tooltip-container { z-index: 51; } + +.audible .aural-only { + z-index: 100; +}