From bdbd792bfef91e07cc703ad96d64ac45a858ef2c Mon Sep 17 00:00:00 2001 From: Andrew Ozz Date: Mon, 14 Mar 2016 00:52:20 +0000 Subject: [PATCH] TinyMCE, inline link: - Add audible confirmation when a link has been selected or inserted in the editor for both the inline dialog and the modal. - Do not auto-search when the URL field is empty or already contains an URL. - Remove a few redundant `tabindex`. Props afercia, azaozz. See #33301. git-svn-id: https://develop.svn.wordpress.org/trunk@36984 602fd350-edb4-49c9-b593-d223f7449a82 --- .../js/tinymce/plugins/wplink/plugin.js | 47 +++++++++++++++++-- src/wp-includes/js/wplink.js | 35 ++++++++++---- src/wp-includes/script-loader.php | 30 ++++++------ 3 files changed, 85 insertions(+), 27 deletions(-) diff --git a/src/wp-includes/js/tinymce/plugins/wplink/plugin.js b/src/wp-includes/js/tinymce/plugins/wplink/plugin.js index 9f1a4eb91e..c426e80128 100644 --- a/src/wp-includes/js/tinymce/plugins/wplink/plugin.js +++ b/src/wp-includes/js/tinymce/plugins/wplink/plugin.js @@ -56,7 +56,7 @@ renderHtml: function() { return ( '' ); @@ -235,6 +235,11 @@ inputInstance.reset(); editor.nodeChanged(); + + // Audible confirmation message when a link has been inserted in the Editor. + if ( typeof window.wp !== 'undefined' && window.wp.a11y && typeof window.wpLinkL10n !== 'undefined' ) { + window.wp.a11y.speak( window.wpLinkL10n.linkInserted ); + } } ); editor.addCommand( 'wp_link_cancel', function() { @@ -371,11 +376,22 @@ }, focus: function( event, ui ) { $input.attr( 'aria-activedescendant', 'mce-wp-autocomplete-' + ui.item.ID ); + /* + * Don't empty the URL input field, when using the arrow keys to + * highlight items. See api.jqueryui.com/autocomplete/#event-focus + */ event.preventDefault(); }, select: function( event, ui ) { $input.val( ui.item.permalink ); $( element.firstChild.nextSibling ).val( ui.item.title ); + + if ( 9 === event.keyCode && typeof window.wp !== 'undefined' && + window.wp.a11y && typeof window.wpLinkL10n !== 'undefined' ) { + // Audible confirmation message when a link has been selected. + window.wp.a11y.speak( window.wpLinkL10n.linkSelected ); + } + return false; }, open: function() { @@ -413,13 +429,21 @@ 'aria-autocomplete': 'list', 'aria-expanded': 'false', 'aria-owns': $input.autocomplete( 'widget' ).attr( 'id' ) - } ) + } ) .on( 'focus', function() { - $input.autocomplete( 'search' ); + var inputValue = $input.val(); + /* + * Don't trigger a search if the URL field already has a link or is empty. + * Also, avoids screen readers announce `No search results`. + */ + if ( inputValue && ! /^https?:/.test( inputValue ) ) { + $input.autocomplete( 'search' ); + } } ) .autocomplete( 'widget' ) .addClass( 'wplink-autocomplete' ) - .attr( 'role', 'listbox' ); + .attr( 'role', 'listbox' ) + .removeAttr( 'tabindex' ); // Remove the `tabindex=0` attribute added by jQuery UI. } tinymce.$( input ).on( 'keydown', function( event ) { @@ -483,7 +507,20 @@ var url = inputInstance.getURL() || null, text = inputInstance.getLinkText() || null; - editor.focus(); // Needed for IE + /* + * Accessibility note: moving focus back to the editor confuses + * screen readers. They will announce again the Editor ARIA role + * `application` and the iframe `title` attribute. + * + * Unfortunately IE looses the selection when the editor iframe + * looses focus, so without returning focus to the editor, the code + * in the modal will not be able to get the selection, place the caret + * at the same location, etc. + */ + if ( tinymce.Env.ie ) { + editor.focus(); // Needed for IE + } + window.wpLink.open( editor.id, url, text, linkNode ); editToolbar.tempHide = true; diff --git a/src/wp-includes/js/wplink.js b/src/wp-includes/js/wplink.js index c32a998449..3851c2fd59 100644 --- a/src/wp-includes/js/wplink.js +++ b/src/wp-includes/js/wplink.js @@ -1,7 +1,7 @@ var wpLink; -( function( $, wpLinkL10n ) { +( function( $, wpLinkL10n, wp ) { var editor, correctedURL, linkNode, inputs = {}, isTouch = ( 'ontouchend' in document ); @@ -76,6 +76,10 @@ var wpLink; }, focus: function( event, ui ) { $input.attr( 'aria-activedescendant', 'mce-wp-autocomplete-' + ui.item.ID ); + /* + * Don't empty the URL input field, when using the arrow keys to + * highlight items. See api.jqueryui.com/autocomplete/#event-focus + */ event.preventDefault(); }, select: function( event, ui ) { @@ -85,6 +89,9 @@ var wpLink; inputs.text.val( ui.item.title ); } + // Audible confirmation message when a link has been selected. + wp.a11y.speak( wpLinkL10n.linkSelected ); + return false; }, open: function() { @@ -117,15 +124,21 @@ var wpLink; $input.attr( { 'aria-owns': $input.autocomplete( 'widget' ).attr( 'id' ) - } ) + } ) .on( 'focus', function() { - $input.autocomplete( 'search' ); + var inputValue = $input.val(); + /* + * Don't trigger a search if the URL field already has a link or is empty. + * Also, avoids screen readers announce `No search results`. + */ + if ( inputValue && ! /^https?:/.test( inputValue ) ) { + $input.autocomplete( 'search' ); + } } ) .autocomplete( 'widget' ) .addClass( 'wplink-autocomplete' ) - .attr( 'role', 'listbox' ); - - + .attr( 'role', 'listbox' ) + .removeAttr( 'tabindex' ); // Remove the `tabindex=0` attribute added by jQuery UI. }, // If URL wasn't corrected last time and doesn't start with http:, https:, ? # or /, prepend http:// @@ -170,7 +183,7 @@ var wpLink; editor = null; } - if ( editor && window.tinymce.isIE && ! editor.windowManager.wplinkBookmark ) { + if ( editor && window.tinymce.isIE ) { editor.windowManager.wplinkBookmark = editor.selection.getBookmark(); } } @@ -402,6 +415,9 @@ var wpLink; wpLink.close(); textarea.focus(); + + // Audible confirmation message when a link has been inserted in the Editor. + wp.a11y.speak( wpLinkL10n.linkInserted ); }, mceUpdate: function() { @@ -450,6 +466,9 @@ var wpLink; wpLink.close( 'noReset' ); editor.focus(); editor.nodeChanged(); + + // Audible confirmation message when a link has been inserted in the Editor. + wp.a11y.speak( wpLinkL10n.linkInserted ); }, keydown: function( event ) { @@ -511,4 +530,4 @@ var wpLink; }; $( document ).ready( wpLink.init ); -})( jQuery, window.wpLinkL10n ); +})( jQuery, window.wpLinkL10n, window.wp ); diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index b40bab76a5..9b4643fc2d 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -204,7 +204,7 @@ function wp_default_scripts( &$scripts ) { $scripts->add( 'jquery-effects-transfer', "/wp-includes/js/jquery/ui/effect-transfer$dev_suffix.js", array('jquery-effects-core'), '1.11.4', 1 ); $scripts->add( 'jquery-ui-accordion', "/wp-includes/js/jquery/ui/accordion$dev_suffix.js", array('jquery-ui-core', 'jquery-ui-widget'), '1.11.4', 1 ); - $scripts->add( 'jquery-ui-autocomplete', "/wp-includes/js/jquery/ui/autocomplete$dev_suffix.js", array('jquery-ui-menu'), '1.11.4', 1 ); + $scripts->add( 'jquery-ui-autocomplete', "/wp-includes/js/jquery/ui/autocomplete$dev_suffix.js", array( 'jquery-ui-menu', 'wp-a11y' ), '1.11.4', 1 ); $scripts->add( 'jquery-ui-button', "/wp-includes/js/jquery/ui/button$dev_suffix.js", array('jquery-ui-core', 'jquery-ui-widget'), '1.11.4', 1 ); $scripts->add( 'jquery-ui-datepicker', "/wp-includes/js/jquery/ui/datepicker$dev_suffix.js", array('jquery-ui-core'), '1.11.4', 1 ); $scripts->add( 'jquery-ui-dialog', "/wp-includes/js/jquery/ui/dialog$dev_suffix.js", array('jquery-ui-resizable', 'jquery-ui-draggable', 'jquery-ui-button', 'jquery-ui-position'), '1.11.4', 1 ); @@ -226,10 +226,10 @@ function wp_default_scripts( &$scripts ) { // Strings for 'jquery-ui-autocomplete' live region messages did_action( 'init' ) && $scripts->localize( 'jquery-ui-autocomplete', 'uiAutocompleteL10n', array( - 'noResults' => __( 'No search results.' ), - /* translators: Number of results found when using jQuery UI Autocomplete */ - 'oneResult' => __( '1 result found. Use up and down arrow keys to navigate.' ), - 'manyResults' => __( '%d results found. Use up and down arrow keys to navigate.' ), + 'noResults' => __( 'No search results.' ), + /* translators: Number of results found when using jQuery UI Autocomplete */ + 'oneResult' => __( '1 result found. Use up and down arrow keys to navigate.' ), + 'manyResults' => __( '%d results found. Use up and down arrow keys to navigate.' ), ) ); // deprecated, not used in core, most functionality is included in jQuery 1.3 @@ -252,13 +252,13 @@ function wp_default_scripts( &$scripts ) { $scripts->add( 'thickbox', "/wp-includes/js/thickbox/thickbox.js", array('jquery'), '3.1-20121105', 1 ); did_action( 'init' ) && $scripts->localize( 'thickbox', 'thickboxL10n', array( - 'next' => __('Next >'), - 'prev' => __('< Prev'), - 'image' => __('Image'), - 'of' => __('of'), - 'close' => __('Close'), - 'noiframes' => __('This feature requires inline frames. You have iframes disabled or your browser does not support them.'), - 'loadingAnimation' => includes_url('js/thickbox/loadingAnimation.gif'), + 'next' => __('Next >'), + 'prev' => __('< Prev'), + 'image' => __('Image'), + 'of' => __('of'), + 'close' => __('Close'), + 'noiframes' => __('This feature requires inline frames. You have iframes disabled or your browser does not support them.'), + 'loadingAnimation' => includes_url('js/thickbox/loadingAnimation.gif'), ) ); $scripts->add( 'jcrop', "/wp-includes/js/jcrop/jquery.Jcrop.min.js", array('jquery'), '0.9.12'); @@ -401,13 +401,15 @@ function wp_default_scripts( &$scripts ) { $scripts->add( 'admin-bar', "/wp-includes/js/admin-bar$suffix.js", array(), false, 1 ); - $scripts->add( 'wplink', "/wp-includes/js/wplink$suffix.js", array( 'jquery' ), false, 1 ); + $scripts->add( 'wplink', "/wp-includes/js/wplink$suffix.js", array( 'jquery', 'wp-a11y' ), false, 1 ); did_action( 'init' ) && $scripts->localize( 'wplink', 'wpLinkL10n', array( 'title' => __('Insert/edit link'), 'update' => __('Update'), 'save' => __('Add Link'), 'noTitle' => __('(no title)'), - 'noMatchesFound' => __('No results found.') + 'noMatchesFound' => __('No results found.'), + 'linkSelected' => __( 'Link selected.' ), + 'linkInserted' => __( 'Link inserted.' ), ) ); $scripts->add( 'wpdialogs', "/wp-includes/js/wpdialog$suffix.js", array( 'jquery-ui-dialog' ), false, 1 );