diff --git a/src/wp-admin/includes/class-wp-posts-list-table.php b/src/wp-admin/includes/class-wp-posts-list-table.php index 506dded3fd..983a274f85 100644 --- a/src/wp-admin/includes/class-wp-posts-list-table.php +++ b/src/wp-admin/includes/class-wp-posts-list-table.php @@ -1550,10 +1550,13 @@ class WP_Posts_List_Table extends WP_List_Table { - cap->assign_terms ) ) : ?> + cap->assign_terms ) ) : + $taxonomy_name = esc_attr( $taxonomy->name ); + + ?> diff --git a/src/wp-admin/includes/meta-boxes.php b/src/wp-admin/includes/meta-boxes.php index b345e6f7ec..98e20de417 100644 --- a/src/wp-admin/includes/meta-boxes.php +++ b/src/wp-admin/includes/meta-boxes.php @@ -431,7 +431,7 @@ function post_tags_meta_box( $post, $box ) {
-

+

labels->separate_items_with_commas; ?>

diff --git a/src/wp-admin/js/inline-edit-post.js b/src/wp-admin/js/inline-edit-post.js index 3a65805e3e..9582707479 100644 --- a/src/wp-admin/js/inline-edit-post.js +++ b/src/wp-admin/js/inline-edit-post.js @@ -83,7 +83,7 @@ inlineEditPost = { }, setBulk : function(){ - var te = '', type = this.type, tax, c = true; + var te = '', type = this.type, c = true; this.revert(); $( '#bulk-edit td' ).attr( 'colspan', $( 'th:visible, td:visible', '.widefat:first thead' ).length ); @@ -114,9 +114,9 @@ inlineEditPost = { // enable autocomplete for tags if ( 'post' === type ) { - // support multi taxonomies? - tax = 'post_tag'; - $('tr.inline-editor textarea[name="tax_input['+tax+']"]').suggest( ajaxurl + '?action=ajax-tag-search&tax=' + tax, { delay: 500, minchars: 2, multiple: true, multipleSep: inlineEditL10n.comma } ); + $( 'tr.inline-editor textarea[data-wp-taxonomy]' ).each( function ( i, element ) { + $( element ).wpTagsSuggest(); + } ); } $('html, body').animate( { scrollTop: 0 }, 'fast' ); }, @@ -196,7 +196,7 @@ inlineEditPost = { textarea.val(terms); } - textarea.suggest( ajaxurl + '?action=ajax-tag-search&tax=' + taxname, { delay: 500, minchars: 2, multiple: true, multipleSep: inlineEditL10n.comma } ); + textarea.wpTagsSuggest(); }); // handle the post status diff --git a/src/wp-admin/js/tags-box.js b/src/wp-admin/js/tags-box.js index af823ac4e6..d4cad9fb24 100644 --- a/src/wp-admin/js/tags-box.js +++ b/src/wp-admin/js/tags-box.js @@ -4,6 +4,8 @@ var tagBox, array_unique_noempty; ( function( $ ) { + var tagDelimiter = ( window.tagsSuggestL10n && window.tagsSuggestL10n.tagDelimiter ) || ','; + // Return an array with any duplicate, whitespace or empty values removed array_unique_noempty = function( array ) { var out = []; @@ -20,13 +22,17 @@ var tagBox, array_unique_noempty; }; tagBox = { - clean : function(tags) { - var comma = window.tagsBoxL10n.tagDelimiter; - if ( ',' !== comma ) - tags = tags.replace(new RegExp(comma, 'g'), ','); + clean : function( tags ) { + if ( ',' !== tagDelimiter ) { + tags = tags.replace( new RegExp( tagDelimiter, 'g' ), ',' ); + } + tags = tags.replace(/\s*,\s*/g, ',').replace(/,+/g, ',').replace(/[,\s]+$/, '').replace(/^[,\s]+/, ''); - if ( ',' !== comma ) - tags = tags.replace(/,/g, comma); + + if ( ',' !== tagDelimiter ) { + tags = tags.replace( /,/g, tagDelimiter ); + } + return tags; }, @@ -35,8 +41,7 @@ var tagBox, array_unique_noempty; num = id.split('-check-num-')[1], taxbox = $(el).closest('.tagsdiv'), thetags = taxbox.find('.the-tags'), - comma = window.tagsBoxL10n.tagDelimiter, - current_tags = thetags.val().split( comma ), + current_tags = thetags.val().split( tagDelimiter ), new_tags = []; delete current_tags[num]; @@ -48,7 +53,7 @@ var tagBox, array_unique_noempty; } }); - thetags.val( this.clean( new_tags.join( comma ) ) ); + thetags.val( this.clean( new_tags.join( tagDelimiter ) ) ); this.quickClicks( taxbox ); return false; @@ -65,7 +70,7 @@ var tagBox, array_unique_noempty; disabled = thetags.prop('disabled'); - current_tags = thetags.val().split( window.tagsBoxL10n.tagDelimiter ); + current_tags = thetags.val().split( tagDelimiter ); tagchecklist.empty(); $.each( current_tags, function( key, val ) { @@ -106,8 +111,7 @@ var tagBox, array_unique_noempty; flushTags : function( el, a, f ) { var tagsval, newtags, text, tags = $( '.the-tags', el ), - newtag = $( 'input.newtag', el ), - comma = window.tagsBoxL10n.tagDelimiter; + newtag = $( 'input.newtag', el ); a = a || false; @@ -118,10 +122,10 @@ var tagBox, array_unique_noempty; } tagsval = tags.val(); - newtags = tagsval ? tagsval + comma + text : text; + newtags = tagsval ? tagsval + tagDelimiter + text : text; newtags = this.clean( newtags ); - newtags = array_unique_noempty( newtags.split( comma ) ).join( comma ); + newtags = array_unique_noempty( newtags.split( tagDelimiter ) ).join( tagDelimiter ); tags.val( newtags ); this.quickClicks( el ); @@ -153,32 +157,29 @@ var tagBox, array_unique_noempty; }, init : function() { - var t = this, ajaxtag = $('div.ajaxtag'); + var ajaxtag = $('div.ajaxtag'); $('.tagsdiv').each( function() { - tagBox.quickClicks(this); + tagBox.quickClicks( this ); }); - $('.tagadd', ajaxtag).click(function(){ - t.flushTags( $(this).closest('.tagsdiv') ); + $( '.tagadd', ajaxtag ).click( function() { + tagBox.flushTags( $( this ).closest( '.tagsdiv' ) ); }); - $('input.newtag', ajaxtag).keyup(function(e){ - if ( 13 == e.which ) { - tagBox.flushTags( $(this).closest('.tagsdiv') ); - return false; + $( 'input.newtag', ajaxtag ).keyup( function( event ) { + if ( 13 == event.which ) { + tagBox.flushTags( $( this ).closest( '.tagsdiv' ) ); + event.preventDefault(); + event.stopPropagation(); } - }).keypress(function(e){ - if ( 13 == e.which ) { - e.preventDefault(); - return false; + }).keypress( function( event ) { + if ( 13 == event.which ) { + event.preventDefault(); + event.stopPropagation(); } - }).each( function() { - var tax = $(this).closest('div.tagsdiv').attr('id'); - $(this).suggest( - ajaxurl + '?action=ajax-tag-search&tax=' + tax, - { delay: 500, minchars: 2, multiple: true, multipleSep: window.tagsBoxL10n.tagDelimiter } - ); + }).each( function( i, element ) { + $( element ).wpTagsSuggest(); }); // save tags on post save/publish diff --git a/src/wp-admin/js/tags-suggest.js b/src/wp-admin/js/tags-suggest.js new file mode 100644 index 0000000000..b9e1632120 --- /dev/null +++ b/src/wp-admin/js/tags-suggest.js @@ -0,0 +1,167 @@ +( function( $ ) { + var tempID = 0; + var separator = ( window.tagsSuggestL10n && window.tagsSuggestL10n.tagDelimiter ) || ','; + + function split( val ) { + return val.split( new RegExp( separator + '\\s*' ) ); + } + + function getLast( term ) { + return split( term ).pop(); + } + + $.fn.wpTagsSuggest = function( options ) { + var cache; + var last; + var $element = $( this ); + + options = options || {}; + + var taxonomy = options.taxonomy || $element.attr( 'data-wp-taxonomy' ) || 'post_tag'; + + delete( options.taxonomy ); + + options = $.extend( { + source: function( request, response ) { + var term; + + if ( last === request.term ) { + response( cache ); + return; + } + + term = getLast( request.term ); + + $.get( window.ajaxurl, { + action: 'ajax-tag-search', + tax: taxonomy, + q: term + } ).always( function() { + $element.removeClass( 'ui-autocomplete-loading' ); // UI fails to remove this sometimes? + } ).done( function( data ) { + var value; + var terms = []; + + if ( data ) { + data = data.split( '\n' ); + + for ( value in data ) { + var id = ++tempID; + + terms.push({ + id: id, + name: data[value] + }); + } + + cache = terms; + response( terms ); + } else { + response( terms ); + } + } ); + + last = request.term; + }, + focus: function( event, ui ) { + $element.attr( 'aria-activedescendant', 'wp-tags-autocomplete-' + ui.item.id ); + + // Don't empty the input field when using the arrow keys to + // highlight items. See api.jqueryui.com/autocomplete/#event-focus + event.preventDefault(); + }, + select: function( event, ui ) { + var tags = split( $element.val() ); + // Remove the last user input. + tags.pop(); + // Append the new tag and an empty element to get one more separator at the end. + tags.push( ui.item.name, '' ); + + $element.val( tags.join( separator + ' ' ) ); + + if ( $.ui.keyCode.TAB === event.keyCode ) { + if ( typeof window.uiAutocompleteL10n !== 'undefined' ) { + // Audible confirmation message when a tag has been selected. + window.wp.a11y.speak( window.uiAutocompleteL10n.itemSelected ); + } + + event.preventDefault(); + } else if ( $.ui.keyCode.ENTER === event.keyCode ) { + // Do not close Quick Edit / Bulk Edit + event.preventDefault(); + event.stopPropagation(); + } + + return false; + }, + open: function() { + $element.attr( 'aria-expanded', 'true' ); + }, + close: function() { + $element.attr( 'aria-expanded', 'false' ); + }, + minLength: 2, + position: { + my: 'left top+2' + }, + messages: { + noResults: ( typeof window.uiAutocompleteL10n !== 'undefined' ) ? window.uiAutocompleteL10n.noResults : '', + results: function( number ) { + if ( typeof window.uiAutocompleteL10n !== 'undefined' ) { + if ( number > 1 ) { + return window.uiAutocompleteL10n.manyResults.replace( '%d', number ); + } + + return window.uiAutocompleteL10n.oneResult; + } + } + } + }, options ); + + $element.on( 'keydown', function() { + $element.removeAttr( 'aria-activedescendant' ); + } ) + .autocomplete( options ) + .autocomplete( 'instance' )._renderItem = function( ul, item ) { + return $( '
  • ' ) + .text( item.name ) + .appendTo( ul ); + }; + + $element.attr( { + 'role': 'combobox', + 'aria-autocomplete': 'list', + 'aria-expanded': 'false', + 'aria-owns': $element.autocomplete( 'widget' ).attr( 'id' ) + } ) + .on( 'focus', function() { + var inputValue = split( $element.val() ).pop(); + + // Don't trigger a search if the field is empty. + // Also, avoids screen readers announce `No search results`. + if ( inputValue ) { + $element.autocomplete( 'search' ); + } + } ) + // Returns a jQuery object containing the menu element. + .autocomplete( 'widget' ) + .addClass( 'wp-tags-autocomplete' ) + .attr( 'role', 'listbox' ) + .removeAttr( 'tabindex' ) // Remove the `tabindex=0` attribute added by jQuery UI. + + // Looks like Safari and VoiceOver need an `aria-selected` attribute. See ticket #33301. + // The `menufocus` and `menublur` events are the same events used to add and remove + // the `ui-state-focus` CSS class on the menu items. See jQuery UI Menu Widget. + .on( 'menufocus', function( event, ui ) { + ui.item.attr( 'aria-selected', 'true' ); + }) + .on( 'menublur', function() { + // The `menublur` event returns an object where the item is `null` + // so we need to find the active item with other means. + $( this ).find( '[aria-selected="true"]' ).removeAttr( 'aria-selected' ); + }); + + return this; + }; + +}( jQuery ) ); diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index a7019b039f..e67997a994 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -234,6 +234,7 @@ function wp_default_scripts( &$scripts ) { 'oneResult' => __( '1 result found. Use up and down arrow keys to navigate.' ), /* translators: %d: Number of results found when using jQuery UI Autocomplete */ 'manyResults' => __( '%d results found. Use up and down arrow keys to navigate.' ), + 'itemSelected' => __( 'Item selected.' ), ) ); // deprecated, not used in core, most functionality is included in jQuery 1.3 @@ -241,7 +242,6 @@ function wp_default_scripts( &$scripts ) { // jQuery plugins $scripts->add( 'jquery-color', "/wp-includes/js/jquery/jquery.color.min.js", array('jquery'), '2.1.1', 1 ); - $scripts->add( 'suggest', "/wp-includes/js/jquery/suggest$suffix.js", array('jquery'), '1.1-20110113', 1 ); $scripts->add( 'schedule', '/wp-includes/js/jquery/jquery.schedule.js', array('jquery'), '20m', 1 ); $scripts->add( 'jquery-query', "/wp-includes/js/jquery/jquery.query.js", array('jquery'), '2.1.7', 1 ); $scripts->add( 'jquery-serialize-object', "/wp-includes/js/jquery/jquery.serialize-object.js", array('jquery'), '0.2', 1 ); @@ -249,6 +249,9 @@ function wp_default_scripts( &$scripts ) { $scripts->add( 'jquery-table-hotkeys', "/wp-includes/js/jquery/jquery.table-hotkeys$suffix.js", array('jquery', 'jquery-hotkeys'), false, 1 ); $scripts->add( 'jquery-touch-punch', "/wp-includes/js/jquery/jquery.ui.touch-punch.js", array('jquery-ui-widget', 'jquery-ui-mouse'), '0.2.2', 1 ); + // Not used any more, registered for backwards compatibility. + $scripts->add( 'suggest', "/wp-includes/js/jquery/suggest$suffix.js", array('jquery'), '1.1-20110113', 1 ); + // Masonry v2 depended on jQuery. v3 does not. The older jquery-masonry handle is a shiv. // It sets jQuery as a dependency, as the theme may have been implicitly loading it this way. $scripts->add( 'imagesloaded', "/wp-includes/js/imagesloaded.min.js", array(), '3.2.0', 1 ); @@ -523,8 +526,10 @@ function wp_default_scripts( &$scripts ) { 'postBoxEmptyString' => __( 'Drag boxes here' ), ) ); - $scripts->add( 'tags-box', "/wp-admin/js/tags-box$suffix.js", array( 'jquery', 'suggest' ), false, 1 ); - did_action( 'init' ) && $scripts->localize( 'tags-box', 'tagsBoxL10n', array( + $scripts->add( 'tags-box', "/wp-admin/js/tags-box$suffix.js", array( 'jquery', 'tags-suggest' ), false, 1 ); + + $scripts->add( 'tags-suggest', "/wp-admin/js/tags-suggest$suffix.js", array( 'jquery-ui-autocomplete', 'wp-a11y' ), false, 1 ); + did_action( 'init' ) && $scripts->localize( 'tags-suggest', 'tagsSuggestL10n', array( 'tagDelimiter' => _x( ',', 'tag delimiter' ), ) ); @@ -584,7 +589,7 @@ function wp_default_scripts( &$scripts ) { $scripts->add( 'theme', "/wp-admin/js/theme$suffix.js", array( 'wp-backbone', 'wp-a11y' ), false, 1 ); - $scripts->add( 'inline-edit-post', "/wp-admin/js/inline-edit-post$suffix.js", array( 'jquery', 'suggest', 'wp-a11y' ), false, 1 ); + $scripts->add( 'inline-edit-post', "/wp-admin/js/inline-edit-post$suffix.js", array( 'jquery', 'tags-suggest', 'wp-a11y' ), false, 1 ); did_action( 'init' ) && $scripts->localize( 'inline-edit-post', 'inlineEditL10n', array( 'error' => __( 'Error while saving the changes.' ), 'ntdeltitle' => __( 'Remove From Bulk Edit' ),