diff --git a/src/docs/contributor/assistive_technologies.diviner b/src/docs/contributor/assistive_technologies.diviner new file mode 100644 index 0000000000..a519bde881 --- /dev/null +++ b/src/docs/contributor/assistive_technologies.diviner @@ -0,0 +1,76 @@ +@title Assistive Technologies +@group developer + +Information about making Phabricator accessible to assistive technologies. + +Overview +======== + +Assistive technologies help people with disabilities use the web. For example, +screen readers can assist people with limited or no eyesight by reading the +contents of pages aloud. + +Phabricator has some support for assistive technologies, and we'd like to have +more support. This document describes how to use the currently available +features to improve the accessibility of Phabricator. + + +Aural-Only Elements +=================== + +The most common issue assistive technologies encounter is buttons, links, or +other elements which only convey information visually (usually through an icon +or image). + +These elements can be made more accessible by providing an aural-only label. +This label will not be displayed by visual browsers, but will be read by screen +readers. + +To add an aural-only label to an element, use `javelin_tag()` with the +`aural` attribute: + + javelin_tag( + 'span', + array( + 'aural' => true, + ), + pht('Aural Label Here')); + +This label should be placed inside the button or link that you are labeling. + +You can also use `aural` on a container to provide an entirely different +replacement element, but should be cautious about doing this. + +NOTE: You must use `javelin_tag()`, not `phutil_tag()`, to get support for +this attribute. + + +Visual-Only Elements +==================== + +Occasionally, a visual element should be hidden from screen readers. This should +be rare, but some textual elements convey very little information or are +otherwise disruptive for aural users. + +This technique can also be used to offer a visual alternative of an element +and a different aural alternative element. However, this should be rare: it is +usually better to adapt a single element to work well for both visual and aural +users. + +You can mark an element as visual-only by using `javelin_tag()` with the +`aural` attribute: + + javelin_tag( + 'span', + array( + 'aural' => false, + ), + $ascii_art); + + +Previewing Aural Pages +====================== + +To verify aural markup, you can add `?__aural__=1` to any page URI. This will +make Phabricator render the page with styles that reveal aural-only elements and +mute visual-only elements. diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php index b4dc6fb09e..1f98ad6da7 100644 --- a/src/view/form/control/PhabricatorRemarkupControl.php +++ b/src/view/form/control/PhabricatorRemarkupControl.php @@ -1,206 +1,206 @@ disableMacro = $disable; return $this; } protected function renderInput() { $id = $this->getID(); if (!$id) { $id = celerity_generate_unique_node_id(); $this->setID($id); } // We need to have this if previews render images, since Ajax can not // currently ship JS or CSS. require_celerity_resource('lightbox-attachment-css'); Javelin::initBehavior( 'aphront-drag-and-drop-textarea', array( 'target' => $id, 'activatedClass' => 'aphront-textarea-drag-and-drop', 'uri' => '/file/dropupload/', )); Javelin::initBehavior( 'phabricator-remarkup-assist', array( 'pht' => array( 'bold text' => pht('bold text'), 'italic text' => pht('italic text'), 'monospaced text' => pht('monospaced text'), 'List Item' => pht('List Item'), 'data' => pht('data'), 'name' => pht('name'), 'URL' => pht('URL'), ), )); Javelin::initBehavior('phabricator-tooltips', array()); $actions = array( 'b' => array( 'tip' => pht('Bold'), ), 'i' => array( 'tip' => pht('Italics'), ), 'tt' => array( 'tip' => pht('Monospaced'), ), 'link' => array( 'tip' => pht('Link'), ), array( 'spacer' => true, ), 'ul' => array( 'tip' => pht('Bulleted List'), ), 'ol' => array( 'tip' => pht('Numbered List'), ), 'code' => array( 'tip' => pht('Code Block'), ), 'table' => array( 'tip' => pht('Table'), ), 'image' => array( 'tip' => pht('Upload File'), ), ); if (!$this->disableMacro and function_exists('imagettftext')) { $actions[] = array( 'spacer' => true, ); $actions['meme'] = array( 'tip' => pht('Meme'), ); } $actions['help'] = array( 'tip' => pht('Help'), 'align' => 'right', 'href' => PhabricatorEnv::getDoclink('Remarkup Reference'), ); $actions[] = array( 'spacer' => true, 'align' => 'right', ); $actions['fullscreen'] = array( 'tip' => pht('Fullscreen Mode'), 'align' => 'right', ); $buttons = array(); foreach ($actions as $action => $spec) { $classes = array(); if (idx($spec, 'align') == 'right') { $classes[] = 'remarkup-assist-right'; } if (idx($spec, 'spacer')) { $classes[] = 'remarkup-assist-separator'; $buttons[] = phutil_tag( 'span', array( 'class' => implode(' ', $classes), ), ''); continue; } else { $classes[] = 'remarkup-assist-button'; } $href = idx($spec, 'href', '#'); if ($href == '#') { $meta = array('action' => $action); $mustcapture = true; $target = null; } else { $meta = array(); $mustcapture = null; $target = '_blank'; } $content = null; $tip = idx($spec, 'tip'); if ($tip) { $meta['tip'] = $tip; - $content = phutil_tag( + $content = javelin_tag( 'span', array( - 'class' => 'aural-only', + 'aural' => true, ), $tip); } require_celerity_resource('sprite-icons-css'); $buttons[] = javelin_tag( 'a', array( 'class' => implode(' ', $classes), 'href' => $href, 'sigil' => 'remarkup-assist has-tooltip', 'meta' => $meta, 'mustcapture' => $mustcapture, 'target' => $target, 'tabindex' => -1, ), phutil_tag( 'div', array( 'class' => 'remarkup-assist sprite-icons remarkup-assist-'.$action, ), $content)); } $buttons = phutil_tag( 'div', array( 'class' => 'remarkup-assist-bar', ), $buttons); $monospaced_textareas = null; $monospaced_textareas_class = null; $user = $this->getUser(); if ($user) { $monospaced_textareas = $user ->loadPreferences() ->getPreference( PhabricatorUserPreferences::PREFERENCE_MONOSPACED_TEXTAREAS); if ($monospaced_textareas == 'enabled') { $monospaced_textareas_class = 'PhabricatorMonospaced'; } } $this->setCustomClass( 'remarkup-assist-textarea '.$monospaced_textareas_class); return javelin_tag( 'div', array( 'sigil' => 'remarkup-assist-control', ), array( $buttons, parent::renderInput(), )); } } diff --git a/src/view/phui/PHUIListItemView.php b/src/view/phui/PHUIListItemView.php index 5360771e3c..399b27ac98 100644 --- a/src/view/phui/PHUIListItemView.php +++ b/src/view/phui/PHUIListItemView.php @@ -1,251 +1,251 @@ aural = $aural; return $this; } public function getAural() { return $this->aural; } public function setOrder($order) { $this->order = $order; return $this; } public function getOrder() { return $this->order; } public function setRenderNameAsTooltip($render_name_as_tooltip) { $this->renderNameAsTooltip = $render_name_as_tooltip; return $this; } public function getRenderNameAsTooltip() { return $this->renderNameAsTooltip; } public function setSelected($selected) { $this->selected = $selected; return $this; } public function getSelected() { return $this->selected; } public function setIcon($icon) { $this->icon = $icon; return $this; } public function setAppIcon($icon) { $this->appIcon = $icon; return $this; } public function getIcon() { return $this->icon; } public function setKey($key) { $this->key = (string)$key; return $this; } public function getKey() { return $this->key; } public function setType($type) { $this->type = $type; return $this; } public function getType() { return $this->type; } public function setHref($href) { $this->href = $href; return $this; } public function getHref() { return $this->href; } public function setName($name) { $this->name = $name; return $this; } public function getName() { return $this->name; } public function setIsExternal($is_external) { $this->isExternal = $is_external; return $this; } public function getIsExternal() { return $this->isExternal; } public function setStatusColor($color) { $this->statusColor = $color; return $this; } protected function getTagName() { return 'li'; } protected function getTagAttributes() { $classes = array(); $classes[] = 'phui-list-item-view'; $classes[] = 'phui-list-item-'.$this->type; if ($this->icon || $this->appIcon) { $classes[] = 'phui-list-item-has-icon'; } if ($this->selected) { $classes[] = 'phui-list-item-selected'; } if ($this->statusColor) { $classes[] = $this->statusColor; } return array( 'class' => $classes, ); } public function setDisabled($disabled) { $this->disabled = $disabled; return $this; } public function getDisabled() { return $this->disabled; } protected function getTagContent() { $name = null; $icon = null; $meta = null; $sigil = null; if ($this->name) { if ($this->getRenderNameAsTooltip()) { $sigil = 'has-tooltip'; $meta = array( 'tip' => $this->name, ); } else { $external = null; if ($this->isExternal) { $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' => implode(' ', $classes), ), array( $this->name, $external, )); } } $aural = null; if ($this->aural !== null) { - $aural = phutil_tag( + $aural = javelin_tag( 'span', array( - 'class' => 'aural-only', + 'aural' => true, ), $this->aural); } if ($this->icon) { $icon_name = $this->icon; if ($this->getDisabled()) { $icon_name .= '-grey'; } $icon = id(new PHUIIconView()) ->addClass('phui-list-item-icon') ->setSpriteSheet(PHUIIconView::SPRITE_ICONS) ->setSpriteIcon($icon_name); } if ($this->appIcon) { $icon = id(new PHUIIconView()) ->addClass('phui-list-item-icon') ->setSpriteSheet(PHUIIconView::SPRITE_APPS) ->setSpriteIcon($this->appIcon); } return javelin_tag( $this->href ? 'a' : 'div', array( 'href' => $this->href, 'class' => $this->href ? 'phui-list-item-href' : null, 'meta' => $meta, 'sigil' => $sigil, ), array( $aural, $icon, $this->renderChildren(), $name, )); } }