tags-box.js000064400000025604150251340050006630 0ustar00/** * @output wp-admin/js/tags-box.js */ /* jshint curly: false, eqeqeq: false */ /* global ajaxurl, tagBox, array_unique_noempty */ ( function( $ ) { var tagDelimiter = wp.i18n._x( ',', 'tag delimiter' ) || ','; /** * Filters unique items and returns a new array. * * Filters all items from an array into a new array containing only the unique * items. This also excludes whitespace or empty values. * * @since 2.8.0 * * @global * * @param {Array} array The array to filter through. * * @return {Array} A new array containing only the unique items. */ window.array_unique_noempty = function( array ) { var out = []; // Trim the values and ensure they are unique. $.each( array, function( key, val ) { val = val || ''; val = val.trim(); if ( val && $.inArray( val, out ) === -1 ) { out.push( val ); } } ); return out; }; /** * The TagBox object. * * Contains functions to create and manage tags that can be associated with a * post. * * @since 2.9.0 * * @global */ window.tagBox = { /** * Cleans up tags by removing redundant characters. * * @since 2.9.0 * * @memberOf tagBox * * @param {string} tags Comma separated tags that need to be cleaned up. * * @return {string} The cleaned up tags. */ 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 ( ',' !== tagDelimiter ) { tags = tags.replace( /,/g, tagDelimiter ); } return tags; }, /** * Parses tags and makes them editable. * * @since 2.9.0 * * @memberOf tagBox * * @param {Object} el The tag element to retrieve the ID from. * * @return {boolean} Always returns false. */ parseTags : function(el) { var id = el.id, num = id.split('-check-num-')[1], taxbox = $(el).closest('.tagsdiv'), thetags = taxbox.find('.the-tags'), current_tags = thetags.val().split( tagDelimiter ), new_tags = []; delete current_tags[num]; // Sanitize the current tags and push them as if they're new tags. $.each( current_tags, function( key, val ) { val = val || ''; val = val.trim(); if ( val ) { new_tags.push( val ); } }); thetags.val( this.clean( new_tags.join( tagDelimiter ) ) ); this.quickClicks( taxbox ); return false; }, /** * Creates clickable links, buttons and fields for adding or editing tags. * * @since 2.9.0 * * @memberOf tagBox * * @param {Object} el The container HTML element. * * @return {void} */ quickClicks : function( el ) { var thetags = $('.the-tags', el), tagchecklist = $('.tagchecklist', el), id = $(el).attr('id'), current_tags, disabled; if ( ! thetags.length ) return; disabled = thetags.prop('disabled'); current_tags = thetags.val().split( tagDelimiter ); tagchecklist.empty(); /** * Creates a delete button if tag editing is enabled, before adding it to the tag list. * * @since 2.5.0 * * @memberOf tagBox * * @param {string} key The index of the current tag. * @param {string} val The value of the current tag. * * @return {void} */ $.each( current_tags, function( key, val ) { var listItem, xbutton; val = val || ''; val = val.trim(); if ( ! val ) return; // Create a new list item, and ensure the text is properly escaped. listItem = $( '
  • ' ).text( val ); // If tags editing isn't disabled, create the X button. if ( ! disabled ) { /* * Build the X buttons, hide the X icon with aria-hidden and * use visually hidden text for screen readers. */ xbutton = $( '' ); /** * Handles the click and keypress event of the tag remove button. * * Makes sure the focus ends up in the tag input field when using * the keyboard to delete the tag. * * @since 4.2.0 * * @param {Event} e The click or keypress event to handle. * * @return {void} */ xbutton.on( 'click keypress', function( e ) { // On click or when using the Enter/Spacebar keys. if ( 'click' === e.type || 13 === e.keyCode || 32 === e.keyCode ) { /* * When using the keyboard, move focus back to the * add new tag field. Note: when releasing the pressed * key this will fire the `keyup` event on the input. */ if ( 13 === e.keyCode || 32 === e.keyCode ) { $( this ).closest( '.tagsdiv' ).find( 'input.newtag' ).trigger( 'focus' ); } tagBox.userAction = 'remove'; tagBox.parseTags( this ); } }); listItem.prepend( ' ' ).prepend( xbutton ); } // Append the list item to the tag list. tagchecklist.append( listItem ); }); // The buttons list is built now, give feedback to screen reader users. tagBox.screenReadersMessage(); }, /** * Adds a new tag. * * Also ensures that the quick links are properly generated. * * @since 2.9.0 * * @memberOf tagBox * * @param {Object} el The container HTML element. * @param {Object|boolean} a When this is an HTML element the text of that * element will be used for the new tag. * @param {number|boolean} f If this value is not passed then the tag input * field is focused. * * @return {boolean} Always returns false. */ flushTags : function( el, a, f ) { var tagsval, newtags, text, tags = $( '.the-tags', el ), newtag = $( 'input.newtag', el ); a = a || false; text = a ? $(a).text() : newtag.val(); /* * Return if there's no new tag or if the input field is empty. * Note: when using the keyboard to add tags, focus is moved back to * the input field and the `keyup` event attached on this field will * fire when releasing the pressed key. Checking also for the field * emptiness avoids to set the tags and call quickClicks() again. */ if ( 'undefined' == typeof( text ) || '' === text ) { return false; } tagsval = tags.val(); newtags = tagsval ? tagsval + tagDelimiter + text : text; newtags = this.clean( newtags ); newtags = array_unique_noempty( newtags.split( tagDelimiter ) ).join( tagDelimiter ); tags.val( newtags ); this.quickClicks( el ); if ( ! a ) newtag.val(''); if ( 'undefined' == typeof( f ) ) newtag.trigger( 'focus' ); return false; }, /** * Retrieves the available tags and creates a tagcloud. * * Retrieves the available tags from the database and creates an interactive * tagcloud. Clicking a tag will add it. * * @since 2.9.0 * * @memberOf tagBox * * @param {string} id The ID to extract the taxonomy from. * * @return {void} */ get : function( id ) { var tax = id.substr( id.indexOf('-') + 1 ); /** * Puts a received tag cloud into a DOM element. * * The tag cloud HTML is generated on the server. * * @since 2.9.0 * * @param {number|string} r The response message from the Ajax call. * @param {string} stat The status of the Ajax request. * * @return {void} */ $.post( ajaxurl, { 'action': 'get-tagcloud', 'tax': tax }, function( r, stat ) { if ( 0 === r || 'success' != stat ) { return; } r = $( '
    ' + r + '
    ' ); /** * Adds a new tag when a tag in the tagcloud is clicked. * * @since 2.9.0 * * @return {boolean} Returns false to prevent the default action. */ $( 'a', r ).on( 'click', function() { tagBox.userAction = 'add'; tagBox.flushTags( $( '#' + tax ), this ); return false; }); $( '#' + id ).after( r ); }); }, /** * Track the user's last action. * * @since 4.7.0 */ userAction: '', /** * Dispatches an audible message to screen readers. * * This will inform the user when a tag has been added or removed. * * @since 4.7.0 * * @return {void} */ screenReadersMessage: function() { var message; switch ( this.userAction ) { case 'remove': message = wp.i18n.__( 'Term removed.' ); break; case 'add': message = wp.i18n.__( 'Term added.' ); break; default: return; } window.wp.a11y.speak( message, 'assertive' ); }, /** * Initializes the tags box by setting up the links, buttons. Sets up event * handling. * * This includes handling of pressing the enter key in the input field and the * retrieval of tag suggestions. * * @since 2.9.0 * * @memberOf tagBox * * @return {void} */ init : function() { var ajaxtag = $('div.ajaxtag'); $('.tagsdiv').each( function() { tagBox.quickClicks( this ); }); $( '.tagadd', ajaxtag ).on( 'click', function() { tagBox.userAction = 'add'; tagBox.flushTags( $( this ).closest( '.tagsdiv' ) ); }); /** * Handles pressing enter on the new tag input field. * * Prevents submitting the post edit form. Uses `keypress` to take * into account Input Method Editor (IME) converters. * * @since 2.9.0 * * @param {Event} event The keypress event that occurred. * * @return {void} */ $( 'input.newtag', ajaxtag ).on( 'keypress', function( event ) { if ( 13 == event.which ) { tagBox.userAction = 'add'; tagBox.flushTags( $( this ).closest( '.tagsdiv' ) ); event.preventDefault(); event.stopPropagation(); } }).each( function( i, element ) { $( element ).wpTagsSuggest(); }); /** * Before a post is saved the value currently in the new tag input field will be * added as a tag. * * @since 2.9.0 * * @return {void} */ $('#post').on( 'submit', function(){ $('div.tagsdiv').each( function() { tagBox.flushTags(this, false, 1); }); }); /** * Handles clicking on the tag cloud link. * * Makes sure the ARIA attributes are set correctly. * * @since 2.9.0 * * @return {void} */ $('.tagcloud-link').on( 'click', function(){ // On the first click, fetch the tag cloud and insert it in the DOM. tagBox.get( $( this ).attr( 'id' ) ); // Update button state, remove previous click event and attach a new one to toggle the cloud. $( this ) .attr( 'aria-expanded', 'true' ) .off() .on( 'click', function() { $( this ) .attr( 'aria-expanded', 'false' === $( this ).attr( 'aria-expanded' ) ? 'true' : 'false' ) .siblings( '.the-tagcloud' ).toggle(); }); }); } }; }( jQuery )); tags-suggest.min.js000064400000004335150251340050010301 0ustar00/*! This file is auto-generated */ !function(s){var u=0,n=wp.i18n._x(",","tag delimiter")||",",t=wp.i18n.__,d=wp.i18n._n,l=wp.i18n.sprintf;function p(e){return e.split(new RegExp(n+"\\s*"))}s.fn.wpTagsSuggest=function(e){var i,o,a,r=s(this);return r.length&&(a=(e=e||{}).taxonomy||r.attr("data-wp-taxonomy")||"post_tag",delete e.taxonomy,e=s.extend({source:function(e,n){var t;o===e.term?n(i):(t=p(e.term).pop(),s.get(window.ajaxurl,{action:"ajax-tag-search",tax:a,q:t,number:20}).always(function(){r.removeClass("ui-autocomplete-loading")}).done(function(e){var t,o=[];if(e){for(t in e=e.split("\n")){var a=++u;o.push({id:a,name:e[t]})}n(i=o)}else n(o)}),o=e.term)},focus:function(e,t){r.attr("aria-activedescendant","wp-tags-autocomplete-"+t.item.id),e.preventDefault()},select:function(e,t){var o=p(r.val());return o.pop(),o.push(t.item.name,""),r.val(o.join(n+" ")),s.ui.keyCode.TAB===e.keyCode?(window.wp.a11y.speak(wp.i18n.__("Term selected."),"assertive"),e.preventDefault()):s.ui.keyCode.ENTER===e.keyCode&&(window.tagBox&&(window.tagBox.userAction="add",window.tagBox.flushTags(s(this).closest(".tagsdiv"))),e.preventDefault(),e.stopPropagation()),!1},open:function(){r.attr("aria-expanded","true")},close:function(){r.attr("aria-expanded","false")},minLength:2,position:{my:"left top+2",at:"left bottom",collision:"none"},messages:{noResults:t("No results found."),results:function(e){return l(d("%d result found. Use up and down arrow keys to navigate.","%d results found. Use up and down arrow keys to navigate.",e),e)}}},e),r.on("keydown",function(){r.removeAttr("aria-activedescendant")}),r.autocomplete(e),r.autocomplete("instance"))&&(r.autocomplete("instance")._renderItem=function(e,t){return s('
  • ').text(t.name).appendTo(e)},r.attr({role:"combobox","aria-autocomplete":"list","aria-expanded":"false","aria-owns":r.autocomplete("widget").attr("id")}).on("focus",function(){p(r.val()).pop()&&r.autocomplete("search")}),r.autocomplete("widget").addClass("wp-tags-autocomplete").attr("role","listbox").removeAttr("tabindex").on("menufocus",function(e,t){t.item.attr("aria-selected","true")}).on("menublur",function(){s(this).find('[aria-selected="true"]').removeAttr("aria-selected")})),this}}(jQuery);common.js000064400000172232150251340050006374 0ustar00/** * @output wp-admin/js/common.js */ /* global setUserSetting, ajaxurl, alert, confirm, pagenow */ /* global columns, screenMeta */ /** * Adds common WordPress functionality to the window. * * @param {jQuery} $ jQuery object. * @param {Object} window The window object. * @param {mixed} undefined Unused. */ ( function( $, window, undefined ) { var $document = $( document ), $window = $( window ), $body = $( document.body ), __ = wp.i18n.__, sprintf = wp.i18n.sprintf; /** * Throws an error for a deprecated property. * * @since 5.5.1 * * @param {string} propName The property that was used. * @param {string} version The version of WordPress that deprecated the property. * @param {string} replacement The property that should have been used. */ function deprecatedProperty( propName, version, replacement ) { var message; if ( 'undefined' !== typeof replacement ) { message = sprintf( /* translators: 1: Deprecated property name, 2: Version number, 3: Alternative property name. */ __( '%1$s is deprecated since version %2$s! Use %3$s instead.' ), propName, version, replacement ); } else { message = sprintf( /* translators: 1: Deprecated property name, 2: Version number. */ __( '%1$s is deprecated since version %2$s with no alternative available.' ), propName, version ); } window.console.warn( message ); } /** * Deprecate all properties on an object. * * @since 5.5.1 * @since 5.6.0 Added the `version` parameter. * * @param {string} name The name of the object, i.e. commonL10n. * @param {object} l10nObject The object to deprecate the properties on. * @param {string} version The version of WordPress that deprecated the property. * * @return {object} The object with all its properties deprecated. */ function deprecateL10nObject( name, l10nObject, version ) { var deprecatedObject = {}; Object.keys( l10nObject ).forEach( function( key ) { var prop = l10nObject[ key ]; var propName = name + '.' + key; if ( 'object' === typeof prop ) { Object.defineProperty( deprecatedObject, key, { get: function() { deprecatedProperty( propName, version, prop.alternative ); return prop.func(); } } ); } else { Object.defineProperty( deprecatedObject, key, { get: function() { deprecatedProperty( propName, version, 'wp.i18n' ); return prop; } } ); } } ); return deprecatedObject; } window.wp.deprecateL10nObject = deprecateL10nObject; /** * Removed in 5.5.0, needed for back-compatibility. * * @since 2.6.0 * @deprecated 5.5.0 */ window.commonL10n = window.commonL10n || { warnDelete: '', dismiss: '', collapseMenu: '', expandMenu: '' }; window.commonL10n = deprecateL10nObject( 'commonL10n', window.commonL10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 3.3.0 * @deprecated 5.5.0 */ window.wpPointerL10n = window.wpPointerL10n || { dismiss: '' }; window.wpPointerL10n = deprecateL10nObject( 'wpPointerL10n', window.wpPointerL10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 4.3.0 * @deprecated 5.5.0 */ window.userProfileL10n = window.userProfileL10n || { warn: '', warnWeak: '', show: '', hide: '', cancel: '', ariaShow: '', ariaHide: '' }; window.userProfileL10n = deprecateL10nObject( 'userProfileL10n', window.userProfileL10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 4.9.6 * @deprecated 5.5.0 */ window.privacyToolsL10n = window.privacyToolsL10n || { noDataFound: '', foundAndRemoved: '', noneRemoved: '', someNotRemoved: '', removalError: '', emailSent: '', noExportFile: '', exportError: '' }; window.privacyToolsL10n = deprecateL10nObject( 'privacyToolsL10n', window.privacyToolsL10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 3.6.0 * @deprecated 5.5.0 */ window.authcheckL10n = { beforeunload: '' }; window.authcheckL10n = window.authcheckL10n || deprecateL10nObject( 'authcheckL10n', window.authcheckL10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 2.8.0 * @deprecated 5.5.0 */ window.tagsl10n = { noPerm: '', broken: '' }; window.tagsl10n = window.tagsl10n || deprecateL10nObject( 'tagsl10n', window.tagsl10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 2.5.0 * @deprecated 5.5.0 */ window.adminCommentsL10n = window.adminCommentsL10n || { hotkeys_highlight_first: { alternative: 'window.adminCommentsSettings.hotkeys_highlight_first', func: function() { return window.adminCommentsSettings.hotkeys_highlight_first; } }, hotkeys_highlight_last: { alternative: 'window.adminCommentsSettings.hotkeys_highlight_last', func: function() { return window.adminCommentsSettings.hotkeys_highlight_last; } }, replyApprove: '', reply: '', warnQuickEdit: '', warnCommentChanges: '', docTitleComments: '', docTitleCommentsCount: '' }; window.adminCommentsL10n = deprecateL10nObject( 'adminCommentsL10n', window.adminCommentsL10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 2.5.0 * @deprecated 5.5.0 */ window.tagsSuggestL10n = window.tagsSuggestL10n || { tagDelimiter: '', removeTerm: '', termSelected: '', termAdded: '', termRemoved: '' }; window.tagsSuggestL10n = deprecateL10nObject( 'tagsSuggestL10n', window.tagsSuggestL10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 3.5.0 * @deprecated 5.5.0 */ window.wpColorPickerL10n = window.wpColorPickerL10n || { clear: '', clearAriaLabel: '', defaultString: '', defaultAriaLabel: '', pick: '', defaultLabel: '' }; window.wpColorPickerL10n = deprecateL10nObject( 'wpColorPickerL10n', window.wpColorPickerL10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 2.7.0 * @deprecated 5.5.0 */ window.attachMediaBoxL10n = window.attachMediaBoxL10n || { error: '' }; window.attachMediaBoxL10n = deprecateL10nObject( 'attachMediaBoxL10n', window.attachMediaBoxL10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 2.5.0 * @deprecated 5.5.0 */ window.postL10n = window.postL10n || { ok: '', cancel: '', publishOn: '', publishOnFuture: '', publishOnPast: '', dateFormat: '', showcomm: '', endcomm: '', publish: '', schedule: '', update: '', savePending: '', saveDraft: '', 'private': '', 'public': '', publicSticky: '', password: '', privatelyPublished: '', published: '', saveAlert: '', savingText: '', permalinkSaved: '' }; window.postL10n = deprecateL10nObject( 'postL10n', window.postL10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 2.7.0 * @deprecated 5.5.0 */ window.inlineEditL10n = window.inlineEditL10n || { error: '', ntdeltitle: '', notitle: '', comma: '', saved: '' }; window.inlineEditL10n = deprecateL10nObject( 'inlineEditL10n', window.inlineEditL10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 2.7.0 * @deprecated 5.5.0 */ window.plugininstallL10n = window.plugininstallL10n || { plugin_information: '', plugin_modal_label: '', ays: '' }; window.plugininstallL10n = deprecateL10nObject( 'plugininstallL10n', window.plugininstallL10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 3.0.0 * @deprecated 5.5.0 */ window.navMenuL10n = window.navMenuL10n || { noResultsFound: '', warnDeleteMenu: '', saveAlert: '', untitled: '' }; window.navMenuL10n = deprecateL10nObject( 'navMenuL10n', window.navMenuL10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 2.5.0 * @deprecated 5.5.0 */ window.commentL10n = window.commentL10n || { submittedOn: '', dateFormat: '' }; window.commentL10n = deprecateL10nObject( 'commentL10n', window.commentL10n, '5.5.0' ); /** * Removed in 5.5.0, needed for back-compatibility. * * @since 2.9.0 * @deprecated 5.5.0 */ window.setPostThumbnailL10n = window.setPostThumbnailL10n || { setThumbnail: '', saving: '', error: '', done: '' }; window.setPostThumbnailL10n = deprecateL10nObject( 'setPostThumbnailL10n', window.setPostThumbnailL10n, '5.5.0' ); /** * Removed in 6.5.0, needed for back-compatibility. * * @since 4.5.0 * @deprecated 6.5.0 */ window.uiAutocompleteL10n = window.uiAutocompleteL10n || { noResults: '', oneResult: '', manyResults: '', itemSelected: '' }; window.uiAutocompleteL10n = deprecateL10nObject( 'uiAutocompleteL10n', window.uiAutocompleteL10n, '6.5.0' ); /** * Removed in 3.3.0, needed for back-compatibility. * * @since 2.7.0 * @deprecated 3.3.0 */ window.adminMenu = { init : function() {}, fold : function() {}, restoreMenuState : function() {}, toggle : function() {}, favorites : function() {} }; // Show/hide/save table columns. window.columns = { /** * Initializes the column toggles in the screen options. * * Binds an onClick event to the checkboxes to show or hide the table columns * based on their toggled state. And persists the toggled state. * * @since 2.7.0 * * @return {void} */ init : function() { var that = this; $('.hide-column-tog', '#adv-settings').on( 'click', function() { var $t = $(this), column = $t.val(); if ( $t.prop('checked') ) that.checked(column); else that.unchecked(column); columns.saveManageColumnsState(); }); }, /** * Saves the toggled state for the columns. * * Saves whether the columns should be shown or hidden on a page. * * @since 3.0.0 * * @return {void} */ saveManageColumnsState : function() { var hidden = this.hidden(); $.post( ajaxurl, { action: 'hidden-columns', hidden: hidden, screenoptionnonce: $('#screenoptionnonce').val(), page: pagenow }, function() { wp.a11y.speak( __( 'Screen Options updated.' ) ); } ); }, /** * Makes a column visible and adjusts the column span for the table. * * @since 3.0.0 * @param {string} column The column name. * * @return {void} */ checked : function(column) { $('.column-' + column).removeClass( 'hidden' ); this.colSpanChange(+1); }, /** * Hides a column and adjusts the column span for the table. * * @since 3.0.0 * @param {string} column The column name. * * @return {void} */ unchecked : function(column) { $('.column-' + column).addClass( 'hidden' ); this.colSpanChange(-1); }, /** * Gets all hidden columns. * * @since 3.0.0 * * @return {string} The hidden column names separated by a comma. */ hidden : function() { return $( '.manage-column[id]' ).filter( '.hidden' ).map(function() { return this.id; }).get().join( ',' ); }, /** * Gets the checked column toggles from the screen options. * * @since 3.0.0 * * @return {string} String containing the checked column names. */ useCheckboxesForHidden : function() { this.hidden = function(){ return $('.hide-column-tog').not(':checked').map(function() { var id = this.id; return id.substring( id, id.length - 5 ); }).get().join(','); }; }, /** * Adjusts the column span for the table. * * @since 3.1.0 * * @param {number} diff The modifier for the column span. */ colSpanChange : function(diff) { var $t = $('table').find('.colspanchange'), n; if ( !$t.length ) return; n = parseInt( $t.attr('colspan'), 10 ) + diff; $t.attr('colspan', n.toString()); } }; $( function() { columns.init(); } ); /** * Validates that the required form fields are not empty. * * @since 2.9.0 * * @param {jQuery} form The form to validate. * * @return {boolean} Returns true if all required fields are not an empty string. */ window.validateForm = function( form ) { return !$( form ) .find( '.form-required' ) .filter( function() { return $( ':input:visible', this ).val() === ''; } ) .addClass( 'form-invalid' ) .find( ':input:visible' ) .on( 'change', function() { $( this ).closest( '.form-invalid' ).removeClass( 'form-invalid' ); } ) .length; }; // Stub for doing better warnings. /** * Shows message pop-up notice or confirmation message. * * @since 2.7.0 * * @type {{warn: showNotice.warn, note: showNotice.note}} * * @return {void} */ window.showNotice = { /** * Shows a delete confirmation pop-up message. * * @since 2.7.0 * * @return {boolean} Returns true if the message is confirmed. */ warn : function() { if ( confirm( __( 'You are about to permanently delete these items from your site.\nThis action cannot be undone.\n\'Cancel\' to stop, \'OK\' to delete.' ) ) ) { return true; } return false; }, /** * Shows an alert message. * * @since 2.7.0 * * @param text The text to display in the message. */ note : function(text) { alert(text); } }; /** * Represents the functions for the meta screen options panel. * * @since 3.2.0 * * @type {{element: null, toggles: null, page: null, init: screenMeta.init, * toggleEvent: screenMeta.toggleEvent, open: screenMeta.open, * close: screenMeta.close}} * * @return {void} */ window.screenMeta = { element: null, // #screen-meta toggles: null, // .screen-meta-toggle page: null, // #wpcontent /** * Initializes the screen meta options panel. * * @since 3.2.0 * * @return {void} */ init: function() { this.element = $('#screen-meta'); this.toggles = $( '#screen-meta-links' ).find( '.show-settings' ); this.page = $('#wpcontent'); this.toggles.on( 'click', this.toggleEvent ); }, /** * Toggles the screen meta options panel. * * @since 3.2.0 * * @return {void} */ toggleEvent: function() { var panel = $( '#' + $( this ).attr( 'aria-controls' ) ); if ( !panel.length ) return; if ( panel.is(':visible') ) screenMeta.close( panel, $(this) ); else screenMeta.open( panel, $(this) ); }, /** * Opens the screen meta options panel. * * @since 3.2.0 * * @param {jQuery} panel The screen meta options panel div. * @param {jQuery} button The toggle button. * * @return {void} */ open: function( panel, button ) { $( '#screen-meta-links' ).find( '.screen-meta-toggle' ).not( button.parent() ).css( 'visibility', 'hidden' ); panel.parent().show(); /** * Sets the focus to the meta options panel and adds the necessary CSS classes. * * @since 3.2.0 * * @return {void} */ panel.slideDown( 'fast', function() { panel.removeClass( 'hidden' ).trigger( 'focus' ); button.addClass( 'screen-meta-active' ).attr( 'aria-expanded', true ); }); $document.trigger( 'screen:options:open' ); }, /** * Closes the screen meta options panel. * * @since 3.2.0 * * @param {jQuery} panel The screen meta options panel div. * @param {jQuery} button The toggle button. * * @return {void} */ close: function( panel, button ) { /** * Hides the screen meta options panel. * * @since 3.2.0 * * @return {void} */ panel.slideUp( 'fast', function() { button.removeClass( 'screen-meta-active' ).attr( 'aria-expanded', false ); $('.screen-meta-toggle').css('visibility', ''); panel.parent().hide(); panel.addClass( 'hidden' ); }); $document.trigger( 'screen:options:close' ); } }; /** * Initializes the help tabs in the help panel. * * @param {Event} e The event object. * * @return {void} */ $('.contextual-help-tabs').on( 'click', 'a', function(e) { var link = $(this), panel; e.preventDefault(); // Don't do anything if the click is for the tab already showing. if ( link.is('.active a') ) return false; // Links. $('.contextual-help-tabs .active').removeClass('active'); link.parent('li').addClass('active'); panel = $( link.attr('href') ); // Panels. $('.help-tab-content').not( panel ).removeClass('active').hide(); panel.addClass('active').show(); }); /** * Update custom permalink structure via buttons. */ var permalinkStructureFocused = false, $permalinkStructure = $( '#permalink_structure' ), $permalinkStructureInputs = $( '.permalink-structure input:radio' ), $permalinkCustomSelection = $( '#custom_selection' ), $availableStructureTags = $( '.form-table.permalink-structure .available-structure-tags button' ); // Change permalink structure input when selecting one of the common structures. $permalinkStructureInputs.on( 'change', function() { if ( 'custom' === this.value ) { return; } $permalinkStructure.val( this.value ); // Update button states after selection. $availableStructureTags.each( function() { changeStructureTagButtonState( $( this ) ); } ); } ); $permalinkStructure.on( 'click input', function() { $permalinkCustomSelection.prop( 'checked', true ); } ); // Check if the permalink structure input field has had focus at least once. $permalinkStructure.on( 'focus', function( event ) { permalinkStructureFocused = true; $( this ).off( event ); } ); /** * Enables or disables a structure tag button depending on its usage. * * If the structure is already used in the custom permalink structure, * it will be disabled. * * @param {Object} button Button jQuery object. */ function changeStructureTagButtonState( button ) { if ( -1 !== $permalinkStructure.val().indexOf( button.text().trim() ) ) { button.attr( 'data-label', button.attr( 'aria-label' ) ); button.attr( 'aria-label', button.attr( 'data-used' ) ); button.attr( 'aria-pressed', true ); button.addClass( 'active' ); } else if ( button.attr( 'data-label' ) ) { button.attr( 'aria-label', button.attr( 'data-label' ) ); button.attr( 'aria-pressed', false ); button.removeClass( 'active' ); } } // Check initial button state. $availableStructureTags.each( function() { changeStructureTagButtonState( $( this ) ); } ); // Observe permalink structure field and disable buttons of tags that are already present. $permalinkStructure.on( 'change', function() { $availableStructureTags.each( function() { changeStructureTagButtonState( $( this ) ); } ); } ); $availableStructureTags.on( 'click', function() { var permalinkStructureValue = $permalinkStructure.val(), selectionStart = $permalinkStructure[ 0 ].selectionStart, selectionEnd = $permalinkStructure[ 0 ].selectionEnd, textToAppend = $( this ).text().trim(), textToAnnounce, newSelectionStart; if ( $( this ).hasClass( 'active' ) ) { textToAnnounce = $( this ).attr( 'data-removed' ); } else { textToAnnounce = $( this ).attr( 'data-added' ); } // Remove structure tag if already part of the structure. if ( -1 !== permalinkStructureValue.indexOf( textToAppend ) ) { permalinkStructureValue = permalinkStructureValue.replace( textToAppend + '/', '' ); $permalinkStructure.val( '/' === permalinkStructureValue ? '' : permalinkStructureValue ); // Announce change to screen readers. $( '#custom_selection_updated' ).text( textToAnnounce ); // Disable button. changeStructureTagButtonState( $( this ) ); return; } // Input field never had focus, move selection to end of input. if ( ! permalinkStructureFocused && 0 === selectionStart && 0 === selectionEnd ) { selectionStart = selectionEnd = permalinkStructureValue.length; } $permalinkCustomSelection.prop( 'checked', true ); // Prepend and append slashes if necessary. if ( '/' !== permalinkStructureValue.substr( 0, selectionStart ).substr( -1 ) ) { textToAppend = '/' + textToAppend; } if ( '/' !== permalinkStructureValue.substr( selectionEnd, 1 ) ) { textToAppend = textToAppend + '/'; } // Insert structure tag at the specified position. $permalinkStructure.val( permalinkStructureValue.substr( 0, selectionStart ) + textToAppend + permalinkStructureValue.substr( selectionEnd ) ); // Announce change to screen readers. $( '#custom_selection_updated' ).text( textToAnnounce ); // Disable button. changeStructureTagButtonState( $( this ) ); // If input had focus give it back with cursor right after appended text. if ( permalinkStructureFocused && $permalinkStructure[0].setSelectionRange ) { newSelectionStart = ( permalinkStructureValue.substr( 0, selectionStart ) + textToAppend ).length; $permalinkStructure[0].setSelectionRange( newSelectionStart, newSelectionStart ); $permalinkStructure.trigger( 'focus' ); } } ); $( function() { var checks, first, last, checked, sliced, mobileEvent, transitionTimeout, focusedRowActions, lastClicked = false, pageInput = $('input.current-page'), currentPage = pageInput.val(), isIOS = /iPhone|iPad|iPod/.test( navigator.userAgent ), isAndroid = navigator.userAgent.indexOf( 'Android' ) !== -1, $adminMenuWrap = $( '#adminmenuwrap' ), $wpwrap = $( '#wpwrap' ), $adminmenu = $( '#adminmenu' ), $overlay = $( '#wp-responsive-overlay' ), $toolbar = $( '#wp-toolbar' ), $toolbarPopups = $toolbar.find( 'a[aria-haspopup="true"]' ), $sortables = $('.meta-box-sortables'), wpResponsiveActive = false, $adminbar = $( '#wpadminbar' ), lastScrollPosition = 0, pinnedMenuTop = false, pinnedMenuBottom = false, menuTop = 0, menuState, menuIsPinned = false, height = { window: $window.height(), wpwrap: $wpwrap.height(), adminbar: $adminbar.height(), menu: $adminMenuWrap.height() }, $headerEnd = $( '.wp-header-end' ); /** * Makes the fly-out submenu header clickable, when the menu is folded. * * @param {Event} e The event object. * * @return {void} */ $adminmenu.on('click.wp-submenu-head', '.wp-submenu-head', function(e){ $(e.target).parent().siblings('a').get(0).click(); }); /** * Collapses the admin menu. * * @return {void} */ $( '#collapse-button' ).on( 'click.collapse-menu', function() { var viewportWidth = getViewportWidth() || 961; // Reset any compensation for submenus near the bottom of the screen. $('#adminmenu div.wp-submenu').css('margin-top', ''); if ( viewportWidth <= 960 ) { if ( $body.hasClass('auto-fold') ) { $body.removeClass('auto-fold').removeClass('folded'); setUserSetting('unfold', 1); setUserSetting('mfold', 'o'); menuState = 'open'; } else { $body.addClass('auto-fold'); setUserSetting('unfold', 0); menuState = 'folded'; } } else { if ( $body.hasClass('folded') ) { $body.removeClass('folded'); setUserSetting('mfold', 'o'); menuState = 'open'; } else { $body.addClass('folded'); setUserSetting('mfold', 'f'); menuState = 'folded'; } } $document.trigger( 'wp-collapse-menu', { state: menuState } ); }); /** * Ensures an admin submenu is within the visual viewport. * * @since 4.1.0 * * @param {jQuery} $menuItem The parent menu item containing the submenu. * * @return {void} */ function adjustSubmenu( $menuItem ) { var bottomOffset, pageHeight, adjustment, theFold, menutop, wintop, maxtop, $submenu = $menuItem.find( '.wp-submenu' ); menutop = $menuItem.offset().top; wintop = $window.scrollTop(); maxtop = menutop - wintop - 30; // max = make the top of the sub almost touch admin bar. bottomOffset = menutop + $submenu.height() + 1; // Bottom offset of the menu. pageHeight = $wpwrap.height(); // Height of the entire page. adjustment = 60 + bottomOffset - pageHeight; theFold = $window.height() + wintop - 50; // The fold. if ( theFold < ( bottomOffset - adjustment ) ) { adjustment = bottomOffset - theFold; } if ( adjustment > maxtop ) { adjustment = maxtop; } if ( adjustment > 1 && $('#wp-admin-bar-menu-toggle').is(':hidden') ) { $submenu.css( 'margin-top', '-' + adjustment + 'px' ); } else { $submenu.css( 'margin-top', '' ); } } if ( 'ontouchstart' in window || /IEMobile\/[1-9]/.test(navigator.userAgent) ) { // Touch screen device. // iOS Safari works with touchstart, the rest work with click. mobileEvent = isIOS ? 'touchstart' : 'click'; /** * Closes any open submenus when touch/click is not on the menu. * * @param {Event} e The event object. * * @return {void} */ $body.on( mobileEvent+'.wp-mobile-hover', function(e) { if ( $adminmenu.data('wp-responsive') ) { return; } if ( ! $( e.target ).closest( '#adminmenu' ).length ) { $adminmenu.find( 'li.opensub' ).removeClass( 'opensub' ); } }); /** * Handles the opening or closing the submenu based on the mobile click|touch event. * * @param {Event} event The event object. * * @return {void} */ $adminmenu.find( 'a.wp-has-submenu' ).on( mobileEvent + '.wp-mobile-hover', function( event ) { var $menuItem = $(this).parent(); if ( $adminmenu.data( 'wp-responsive' ) ) { return; } /* * Show the sub instead of following the link if: * - the submenu is not open. * - the submenu is not shown inline or the menu is not folded. */ if ( ! $menuItem.hasClass( 'opensub' ) && ( ! $menuItem.hasClass( 'wp-menu-open' ) || $menuItem.width() < 40 ) ) { event.preventDefault(); adjustSubmenu( $menuItem ); $adminmenu.find( 'li.opensub' ).removeClass( 'opensub' ); $menuItem.addClass('opensub'); } }); } if ( ! isIOS && ! isAndroid ) { $adminmenu.find( 'li.wp-has-submenu' ).hoverIntent({ /** * Opens the submenu when hovered over the menu item for desktops. * * @return {void} */ over: function() { var $menuItem = $( this ), $submenu = $menuItem.find( '.wp-submenu' ), top = parseInt( $submenu.css( 'top' ), 10 ); if ( isNaN( top ) || top > -5 ) { // The submenu is visible. return; } if ( $adminmenu.data( 'wp-responsive' ) ) { // The menu is in responsive mode, bail. return; } adjustSubmenu( $menuItem ); $adminmenu.find( 'li.opensub' ).removeClass( 'opensub' ); $menuItem.addClass( 'opensub' ); }, /** * Closes the submenu when no longer hovering the menu item. * * @return {void} */ out: function(){ if ( $adminmenu.data( 'wp-responsive' ) ) { // The menu is in responsive mode, bail. return; } $( this ).removeClass( 'opensub' ).find( '.wp-submenu' ).css( 'margin-top', '' ); }, timeout: 200, sensitivity: 7, interval: 90 }); /** * Opens the submenu on when focused on the menu item. * * @param {Event} event The event object. * * @return {void} */ $adminmenu.on( 'focus.adminmenu', '.wp-submenu a', function( event ) { if ( $adminmenu.data( 'wp-responsive' ) ) { // The menu is in responsive mode, bail. return; } $( event.target ).closest( 'li.menu-top' ).addClass( 'opensub' ); /** * Closes the submenu on blur from the menu item. * * @param {Event} event The event object. * * @return {void} */ }).on( 'blur.adminmenu', '.wp-submenu a', function( event ) { if ( $adminmenu.data( 'wp-responsive' ) ) { return; } $( event.target ).closest( 'li.menu-top' ).removeClass( 'opensub' ); /** * Adjusts the size for the submenu. * * @return {void} */ }).find( 'li.wp-has-submenu.wp-not-current-submenu' ).on( 'focusin.adminmenu', function() { adjustSubmenu( $( this ) ); }); } /* * The `.below-h2` class is here just for backward compatibility with plugins * that are (incorrectly) using it. Do not use. Use `.inline` instead. See #34570. * If '.wp-header-end' is found, append the notices after it otherwise * after the first h1 or h2 heading found within the main content. */ if ( ! $headerEnd.length ) { $headerEnd = $( '.wrap h1, .wrap h2' ).first(); } $( 'div.updated, div.error, div.notice' ).not( '.inline, .below-h2' ).insertAfter( $headerEnd ); /** * Makes notices dismissible. * * @since 4.4.0 * * @return {void} */ function makeNoticesDismissible() { $( '.notice.is-dismissible' ).each( function() { var $el = $( this ), $button = $( '' ); if ( $el.find( '.notice-dismiss' ).length ) { return; } // Ensure plain text. $button.find( '.screen-reader-text' ).text( __( 'Dismiss this notice.' ) ); $button.on( 'click.wp-dismiss-notice', function( event ) { event.preventDefault(); $el.fadeTo( 100, 0, function() { $el.slideUp( 100, function() { $el.remove(); }); }); }); $el.append( $button ); }); } $document.on( 'wp-updates-notice-added wp-plugin-install-error wp-plugin-update-error wp-plugin-delete-error wp-theme-install-error wp-theme-delete-error wp-notice-added', makeNoticesDismissible ); // Init screen meta. screenMeta.init(); /** * Checks a checkbox. * * This event needs to be delegated. Ticket #37973. * * @return {boolean} Returns whether a checkbox is checked or not. */ $body.on( 'click', 'tbody > tr > .check-column :checkbox', function( event ) { // Shift click to select a range of checkboxes. if ( 'undefined' == event.shiftKey ) { return true; } if ( event.shiftKey ) { if ( !lastClicked ) { return true; } checks = $( lastClicked ).closest( 'form' ).find( ':checkbox' ).filter( ':visible:enabled' ); first = checks.index( lastClicked ); last = checks.index( this ); checked = $(this).prop('checked'); if ( 0 < first && 0 < last && first != last ) { sliced = ( last > first ) ? checks.slice( first, last ) : checks.slice( last, first ); sliced.prop( 'checked', function() { if ( $(this).closest('tr').is(':visible') ) return checked; return false; }); } } lastClicked = this; // Toggle the "Select all" checkboxes depending if the other ones are all checked or not. var unchecked = $(this).closest('tbody').find('tr').find(':checkbox').filter(':visible:enabled').not(':checked'); /** * Determines if all checkboxes are checked. * * @return {boolean} Returns true if there are no unchecked checkboxes. */ $(this).closest('table').children('thead, tfoot').find(':checkbox').prop('checked', function() { return ( 0 === unchecked.length ); }); return true; }); /** * Controls all the toggles on bulk toggle change. * * When the bulk checkbox is changed, all the checkboxes in the tables are changed accordingly. * When the shift-button is pressed while changing the bulk checkbox the checkboxes in the table are inverted. * * This event needs to be delegated. Ticket #37973. * * @param {Event} event The event object. * * @return {boolean} */ $body.on( 'click.wp-toggle-checkboxes', 'thead .check-column :checkbox, tfoot .check-column :checkbox', function( event ) { var $this = $(this), $table = $this.closest( 'table' ), controlChecked = $this.prop('checked'), toggle = event.shiftKey || $this.data('wp-toggle'); $table.children( 'tbody' ).filter(':visible') .children().children('.check-column').find(':checkbox') /** * Updates the checked state on the checkbox in the table. * * @return {boolean} True checks the checkbox, False unchecks the checkbox. */ .prop('checked', function() { if ( $(this).is(':hidden,:disabled') ) { return false; } if ( toggle ) { return ! $(this).prop( 'checked' ); } else if ( controlChecked ) { return true; } return false; }); $table.children('thead, tfoot').filter(':visible') .children().children('.check-column').find(':checkbox') /** * Syncs the bulk checkboxes on the top and bottom of the table. * * @return {boolean} True checks the checkbox, False unchecks the checkbox. */ .prop('checked', function() { if ( toggle ) { return false; } else if ( controlChecked ) { return true; } return false; }); }); /** * Marries a secondary control to its primary control. * * @param {jQuery} topSelector The top selector element. * @param {jQuery} topSubmit The top submit element. * @param {jQuery} bottomSelector The bottom selector element. * @param {jQuery} bottomSubmit The bottom submit element. * @return {void} */ function marryControls( topSelector, topSubmit, bottomSelector, bottomSubmit ) { /** * Updates the primary selector when the secondary selector is changed. * * @since 5.7.0 * * @return {void} */ function updateTopSelector() { topSelector.val($(this).val()); } bottomSelector.on('change', updateTopSelector); /** * Updates the secondary selector when the primary selector is changed. * * @since 5.7.0 * * @return {void} */ function updateBottomSelector() { bottomSelector.val($(this).val()); } topSelector.on('change', updateBottomSelector); /** * Triggers the primary submit when then secondary submit is clicked. * * @since 5.7.0 * * @return {void} */ function triggerSubmitClick(e) { e.preventDefault(); e.stopPropagation(); topSubmit.trigger('click'); } bottomSubmit.on('click', triggerSubmitClick); } // Marry the secondary "Bulk actions" controls to the primary controls: marryControls( $('#bulk-action-selector-top'), $('#doaction'), $('#bulk-action-selector-bottom'), $('#doaction2') ); // Marry the secondary "Change role to" controls to the primary controls: marryControls( $('#new_role'), $('#changeit'), $('#new_role2'), $('#changeit2') ); var addAdminNotice = function( data ) { var $notice = $( data.selector ), $headerEnd = $( '.wp-header-end' ), type, dismissible, $adminNotice; delete data.selector; dismissible = ( data.dismissible && data.dismissible === true ) ? ' is-dismissible' : ''; type = ( data.type ) ? data.type : 'info'; $adminNotice = '

    ' + data.message + '

    '; // Check if this admin notice already exists. if ( ! $notice.length ) { $notice = $( '#' + data.id ); } if ( $notice.length ) { $notice.replaceWith( $adminNotice ); } else if ( $headerEnd.length ) { $headerEnd.after( $adminNotice ); } else { if ( 'customize' === pagenow ) { $( '.customize-themes-notifications' ).append( $adminNotice ); } else { $( '.wrap' ).find( '> h1' ).after( $adminNotice ); } } $document.trigger( 'wp-notice-added' ); }; $( '.bulkactions' ).parents( 'form' ).on( 'submit', function( event ) { var form = this, submitterName = event.originalEvent && event.originalEvent.submitter ? event.originalEvent.submitter.name : false, currentPageSelector = form.querySelector( '#current-page-selector' ); if ( currentPageSelector && currentPageSelector.defaultValue !== currentPageSelector.value ) { return; // Pagination form submission. } // Observe submissions from posts lists for 'bulk_action' or users lists for 'new_role'. var bulkFieldRelations = { 'bulk_action' : window.bulkActionObserverIds.bulk_action, 'changeit' : window.bulkActionObserverIds.changeit }; if ( ! Object.keys( bulkFieldRelations ).includes( submitterName ) ) { return; } var values = new FormData(form); var value = values.get( bulkFieldRelations[ submitterName ] ) || '-1'; // Check that the action is not the default one. if ( value !== '-1' ) { // Check that at least one item is selected. var itemsSelected = form.querySelectorAll( '.wp-list-table tbody .check-column input[type="checkbox"]:checked' ); if ( itemsSelected.length > 0 ) { return; } } event.preventDefault(); event.stopPropagation(); $( 'html, body' ).animate( { scrollTop: 0 } ); var errorMessage = __( 'Please select at least one item to perform this action on.' ); addAdminNotice( { id: 'no-items-selected', type: 'error', message: errorMessage, dismissible: true, } ); wp.a11y.speak( errorMessage ); }); /** * Shows row actions on focus of its parent container element or any other elements contained within. * * @return {void} */ $( '#wpbody-content' ).on({ focusin: function() { clearTimeout( transitionTimeout ); focusedRowActions = $( this ).find( '.row-actions' ); // transitionTimeout is necessary for Firefox, but Chrome won't remove the CSS class without a little help. $( '.row-actions' ).not( this ).removeClass( 'visible' ); focusedRowActions.addClass( 'visible' ); }, focusout: function() { // Tabbing between post title and .row-actions links needs a brief pause, otherwise // the .row-actions div gets hidden in transit in some browsers (ahem, Firefox). transitionTimeout = setTimeout( function() { focusedRowActions.removeClass( 'visible' ); }, 30 ); } }, '.table-view-list .has-row-actions' ); // Toggle list table rows on small screens. $( 'tbody' ).on( 'click', '.toggle-row', function() { $( this ).closest( 'tr' ).toggleClass( 'is-expanded' ); }); $('#default-password-nag-no').on( 'click', function() { setUserSetting('default_password_nag', 'hide'); $('div.default-password-nag').hide(); return false; }); /** * Handles tab keypresses in theme and plugin file editor textareas. * * @param {Event} e The event object. * * @return {void} */ $('#newcontent').on('keydown.wpevent_InsertTab', function(e) { var el = e.target, selStart, selEnd, val, scroll, sel; // After pressing escape key (keyCode: 27), the tab key should tab out of the textarea. if ( e.keyCode == 27 ) { // When pressing Escape: Opera 12 and 27 blur form fields, IE 8 clears them. e.preventDefault(); $(el).data('tab-out', true); return; } // Only listen for plain tab key (keyCode: 9) without any modifiers. if ( e.keyCode != 9 || e.ctrlKey || e.altKey || e.shiftKey ) return; // After tabbing out, reset it so next time the tab key can be used again. if ( $(el).data('tab-out') ) { $(el).data('tab-out', false); return; } selStart = el.selectionStart; selEnd = el.selectionEnd; val = el.value; // If any text is selected, replace the selection with a tab character. if ( document.selection ) { el.focus(); sel = document.selection.createRange(); sel.text = '\t'; } else if ( selStart >= 0 ) { scroll = this.scrollTop; el.value = val.substring(0, selStart).concat('\t', val.substring(selEnd) ); el.selectionStart = el.selectionEnd = selStart + 1; this.scrollTop = scroll; } // Cancel the regular tab functionality, to prevent losing focus of the textarea. if ( e.stopPropagation ) e.stopPropagation(); if ( e.preventDefault ) e.preventDefault(); }); // Reset page number variable for new filters/searches but not for bulk actions. See #17685. if ( pageInput.length ) { /** * Handles pagination variable when filtering the list table. * * Set the pagination argument to the first page when the post-filter form is submitted. * This happens when pressing the 'filter' button on the list table page. * * The pagination argument should not be touched when the bulk action dropdowns are set to do anything. * * The form closest to the pageInput is the post-filter form. * * @return {void} */ pageInput.closest('form').on( 'submit', function() { /* * action = bulk action dropdown at the top of the table */ if ( $('select[name="action"]').val() == -1 && pageInput.val() == currentPage ) pageInput.val('1'); }); } /** * Resets the bulk actions when the search button is clicked. * * @return {void} */ $('.search-box input[type="search"], .search-box input[type="submit"]').on( 'mousedown', function () { $('select[name^="action"]').val('-1'); }); /** * Scrolls into view when focus.scroll-into-view is triggered. * * @param {Event} e The event object. * * @return {void} */ $('#contextual-help-link, #show-settings-link').on( 'focus.scroll-into-view', function(e){ if ( e.target.scrollIntoViewIfNeeded ) e.target.scrollIntoViewIfNeeded(false); }); /** * Disables the submit upload buttons when no data is entered. * * @return {void} */ (function(){ var button, input, form = $('form.wp-upload-form'); // Exit when no upload form is found. if ( ! form.length ) return; button = form.find('input[type="submit"]'); input = form.find('input[type="file"]'); /** * Determines if any data is entered in any file upload input. * * @since 3.5.0 * * @return {void} */ function toggleUploadButton() { // When no inputs have a value, disable the upload buttons. button.prop('disabled', '' === input.map( function() { return $(this).val(); }).get().join('')); } // Update the status initially. toggleUploadButton(); // Update the status when any file input changes. input.on('change', toggleUploadButton); })(); /** * Pins the menu while distraction-free writing is enabled. * * @param {Event} event Event data. * * @since 4.1.0 * * @return {void} */ function pinMenu( event ) { var windowPos = $window.scrollTop(), resizing = ! event || event.type !== 'scroll'; if ( isIOS || $adminmenu.data( 'wp-responsive' ) ) { return; } /* * When the menu is higher than the window and smaller than the entire page. * It should be adjusted to be able to see the entire menu. * * Otherwise it can be accessed normally. */ if ( height.menu + height.adminbar < height.window || height.menu + height.adminbar + 20 > height.wpwrap ) { unpinMenu(); return; } menuIsPinned = true; // If the menu is higher than the window, compensate on scroll. if ( height.menu + height.adminbar > height.window ) { // Check for overscrolling, this happens when swiping up at the top of the document in modern browsers. if ( windowPos < 0 ) { // Stick the menu to the top. if ( ! pinnedMenuTop ) { pinnedMenuTop = true; pinnedMenuBottom = false; $adminMenuWrap.css({ position: 'fixed', top: '', bottom: '' }); } return; } else if ( windowPos + height.window > $document.height() - 1 ) { // When overscrolling at the bottom, stick the menu to the bottom. if ( ! pinnedMenuBottom ) { pinnedMenuBottom = true; pinnedMenuTop = false; $adminMenuWrap.css({ position: 'fixed', top: '', bottom: 0 }); } return; } if ( windowPos > lastScrollPosition ) { // When a down scroll has been detected. // If it was pinned to the top, unpin and calculate relative scroll. if ( pinnedMenuTop ) { pinnedMenuTop = false; // Calculate new offset position. menuTop = $adminMenuWrap.offset().top - height.adminbar - ( windowPos - lastScrollPosition ); if ( menuTop + height.menu + height.adminbar < windowPos + height.window ) { menuTop = windowPos + height.window - height.menu - height.adminbar; } $adminMenuWrap.css({ position: 'absolute', top: menuTop, bottom: '' }); } else if ( ! pinnedMenuBottom && $adminMenuWrap.offset().top + height.menu < windowPos + height.window ) { // Pin it to the bottom. pinnedMenuBottom = true; $adminMenuWrap.css({ position: 'fixed', top: '', bottom: 0 }); } } else if ( windowPos < lastScrollPosition ) { // When a scroll up is detected. // If it was pinned to the bottom, unpin and calculate relative scroll. if ( pinnedMenuBottom ) { pinnedMenuBottom = false; // Calculate new offset position. menuTop = $adminMenuWrap.offset().top - height.adminbar + ( lastScrollPosition - windowPos ); if ( menuTop + height.menu > windowPos + height.window ) { menuTop = windowPos; } $adminMenuWrap.css({ position: 'absolute', top: menuTop, bottom: '' }); } else if ( ! pinnedMenuTop && $adminMenuWrap.offset().top >= windowPos + height.adminbar ) { // Pin it to the top. pinnedMenuTop = true; $adminMenuWrap.css({ position: 'fixed', top: '', bottom: '' }); } } else if ( resizing ) { // Window is being resized. pinnedMenuTop = pinnedMenuBottom = false; // Calculate the new offset. menuTop = windowPos + height.window - height.menu - height.adminbar - 1; if ( menuTop > 0 ) { $adminMenuWrap.css({ position: 'absolute', top: menuTop, bottom: '' }); } else { unpinMenu(); } } } lastScrollPosition = windowPos; } /** * Determines the height of certain elements. * * @since 4.1.0 * * @return {void} */ function resetHeights() { height = { window: $window.height(), wpwrap: $wpwrap.height(), adminbar: $adminbar.height(), menu: $adminMenuWrap.height() }; } /** * Unpins the menu. * * @since 4.1.0 * * @return {void} */ function unpinMenu() { if ( isIOS || ! menuIsPinned ) { return; } pinnedMenuTop = pinnedMenuBottom = menuIsPinned = false; $adminMenuWrap.css({ position: '', top: '', bottom: '' }); } /** * Pins and unpins the menu when applicable. * * @since 4.1.0 * * @return {void} */ function setPinMenu() { resetHeights(); if ( $adminmenu.data('wp-responsive') ) { $body.removeClass( 'sticky-menu' ); unpinMenu(); } else if ( height.menu + height.adminbar > height.window ) { pinMenu(); $body.removeClass( 'sticky-menu' ); } else { $body.addClass( 'sticky-menu' ); unpinMenu(); } } if ( ! isIOS ) { $window.on( 'scroll.pin-menu', pinMenu ); $document.on( 'tinymce-editor-init.pin-menu', function( event, editor ) { editor.on( 'wp-autoresize', resetHeights ); }); } /** * Changes the sortables and responsiveness of metaboxes. * * @since 3.8.0 * * @return {void} */ window.wpResponsive = { /** * Initializes the wpResponsive object. * * @since 3.8.0 * * @return {void} */ init: function() { var self = this; this.maybeDisableSortables = this.maybeDisableSortables.bind( this ); // Modify functionality based on custom activate/deactivate event. $document.on( 'wp-responsive-activate.wp-responsive', function() { self.activate(); self.toggleAriaHasPopup( 'add' ); }).on( 'wp-responsive-deactivate.wp-responsive', function() { self.deactivate(); self.toggleAriaHasPopup( 'remove' ); }); $( '#wp-admin-bar-menu-toggle a' ).attr( 'aria-expanded', 'false' ); // Toggle sidebar when toggle is clicked. $( '#wp-admin-bar-menu-toggle' ).on( 'click.wp-responsive', function( event ) { event.preventDefault(); // Close any open toolbar submenus. $adminbar.find( '.hover' ).removeClass( 'hover' ); $wpwrap.toggleClass( 'wp-responsive-open' ); if ( $wpwrap.hasClass( 'wp-responsive-open' ) ) { $(this).find('a').attr( 'aria-expanded', 'true' ); $( '#adminmenu a:first' ).trigger( 'focus' ); } else { $(this).find('a').attr( 'aria-expanded', 'false' ); } } ); // Close sidebar when target moves outside of toggle and sidebar. $( document ).on( 'click', function( event ) { if ( ! $wpwrap.hasClass( 'wp-responsive-open' ) || ! document.hasFocus() ) { return; } var focusIsInToggle = $.contains( $( '#wp-admin-bar-menu-toggle' )[0], event.target ); var focusIsInSidebar = $.contains( $( '#adminmenuwrap' )[0], event.target ); if ( ! focusIsInToggle && ! focusIsInSidebar ) { $( '#wp-admin-bar-menu-toggle' ).trigger( 'click.wp-responsive' ); } } ); // Close sidebar when a keypress completes outside of toggle and sidebar. $( document ).on( 'keyup', function( event ) { var toggleButton = $( '#wp-admin-bar-menu-toggle' )[0]; if ( ! $wpwrap.hasClass( 'wp-responsive-open' ) ) { return; } if ( 27 === event.keyCode ) { $( toggleButton ).trigger( 'click.wp-responsive' ); $( toggleButton ).find( 'a' ).trigger( 'focus' ); } else { if ( 9 === event.keyCode ) { var sidebar = $( '#adminmenuwrap' )[0]; var focusedElement = event.relatedTarget || document.activeElement; // A brief delay is required to allow focus to switch to another element. setTimeout( function() { var focusIsInToggle = $.contains( toggleButton, focusedElement ); var focusIsInSidebar = $.contains( sidebar, focusedElement ); if ( ! focusIsInToggle && ! focusIsInSidebar ) { $( toggleButton ).trigger( 'click.wp-responsive' ); } }, 10 ); } } }); // Add menu events. $adminmenu.on( 'click.wp-responsive', 'li.wp-has-submenu > a', function( event ) { if ( ! $adminmenu.data('wp-responsive') ) { return; } let state = ( 'false' === $( this ).attr( 'aria-expanded' ) ) ? 'true' : 'false'; $( this ).parent( 'li' ).toggleClass( 'selected' ); $( this ).attr( 'aria-expanded', state ); $( this ).trigger( 'focus' ); event.preventDefault(); }); self.trigger(); $document.on( 'wp-window-resized.wp-responsive', this.trigger.bind( this ) ); // This needs to run later as UI Sortable may be initialized when the document is ready. $window.on( 'load.wp-responsive', this.maybeDisableSortables ); $document.on( 'postbox-toggled', this.maybeDisableSortables ); // When the screen columns are changed, potentially disable sortables. $( '#screen-options-wrap input' ).on( 'click', this.maybeDisableSortables ); }, /** * Disable sortables if there is only one metabox, or the screen is in one column mode. Otherwise, enable sortables. * * @since 5.3.0 * * @return {void} */ maybeDisableSortables: function() { var width = navigator.userAgent.indexOf('AppleWebKit/') > -1 ? $window.width() : window.innerWidth; if ( ( width <= 782 ) || ( 1 >= $sortables.find( '.ui-sortable-handle:visible' ).length && jQuery( '.columns-prefs-1 input' ).prop( 'checked' ) ) ) { this.disableSortables(); } else { this.enableSortables(); } }, /** * Changes properties of body and admin menu. * * Pins and unpins the menu and adds the auto-fold class to the body. * Makes the admin menu responsive and disables the metabox sortables. * * @since 3.8.0 * * @return {void} */ activate: function() { setPinMenu(); if ( ! $body.hasClass( 'auto-fold' ) ) { $body.addClass( 'auto-fold' ); } $adminmenu.data( 'wp-responsive', 1 ); this.disableSortables(); }, /** * Changes properties of admin menu and enables metabox sortables. * * Pin and unpin the menu. * Removes the responsiveness of the admin menu and enables the metabox sortables. * * @since 3.8.0 * * @return {void} */ deactivate: function() { setPinMenu(); $adminmenu.removeData('wp-responsive'); this.maybeDisableSortables(); }, /** * Toggles the aria-haspopup attribute for the responsive admin menu. * * The aria-haspopup attribute is only necessary for the responsive menu. * See ticket https://core.trac.wordpress.org/ticket/43095 * * @since 6.6.0 * * @param {string} action Whether to add or remove the aria-haspopup attribute. * * @return {void} */ toggleAriaHasPopup: function( action ) { var elements = $adminmenu.find( '[data-ariahaspopup]' ); if ( action === 'add' ) { elements.each( function() { $( this ).attr( 'aria-haspopup', 'menu' ).attr( 'aria-expanded', 'false' ); } ); return; } elements.each( function() { $( this ).removeAttr( 'aria-haspopup' ).removeAttr( 'aria-expanded' ); } ); }, /** * Sets the responsiveness and enables the overlay based on the viewport width. * * @since 3.8.0 * * @return {void} */ trigger: function() { var viewportWidth = getViewportWidth(); // Exclude IE < 9, it doesn't support @media CSS rules. if ( ! viewportWidth ) { return; } if ( viewportWidth <= 782 ) { if ( ! wpResponsiveActive ) { $document.trigger( 'wp-responsive-activate' ); wpResponsiveActive = true; } } else { if ( wpResponsiveActive ) { $document.trigger( 'wp-responsive-deactivate' ); wpResponsiveActive = false; } } if ( viewportWidth <= 480 ) { this.enableOverlay(); } else { this.disableOverlay(); } this.maybeDisableSortables(); }, /** * Inserts a responsive overlay and toggles the window. * * @since 3.8.0 * * @return {void} */ enableOverlay: function() { if ( $overlay.length === 0 ) { $overlay = $( '
    ' ) .insertAfter( '#wpcontent' ) .hide() .on( 'click.wp-responsive', function() { $toolbar.find( '.menupop.hover' ).removeClass( 'hover' ); $( this ).hide(); }); } $toolbarPopups.on( 'click.wp-responsive', function() { $overlay.show(); }); }, /** * Disables the responsive overlay and removes the overlay. * * @since 3.8.0 * * @return {void} */ disableOverlay: function() { $toolbarPopups.off( 'click.wp-responsive' ); $overlay.hide(); }, /** * Disables sortables. * * @since 3.8.0 * * @return {void} */ disableSortables: function() { if ( $sortables.length ) { try { $sortables.sortable( 'disable' ); $sortables.find( '.ui-sortable-handle' ).addClass( 'is-non-sortable' ); } catch ( e ) {} } }, /** * Enables sortables. * * @since 3.8.0 * * @return {void} */ enableSortables: function() { if ( $sortables.length ) { try { $sortables.sortable( 'enable' ); $sortables.find( '.ui-sortable-handle' ).removeClass( 'is-non-sortable' ); } catch ( e ) {} } } }; /** * Add an ARIA role `button` to elements that behave like UI controls when JavaScript is on. * * @since 4.5.0 * * @return {void} */ function aria_button_if_js() { $( '.aria-button-if-js' ).attr( 'role', 'button' ); } $( document ).on( 'ajaxComplete', function() { aria_button_if_js(); }); /** * Get the viewport width. * * @since 4.7.0 * * @return {number|boolean} The current viewport width or false if the * browser doesn't support innerWidth (IE < 9). */ function getViewportWidth() { var viewportWidth = false; if ( window.innerWidth ) { // On phones, window.innerWidth is affected by zooming. viewportWidth = Math.max( window.innerWidth, document.documentElement.clientWidth ); } return viewportWidth; } /** * Sets the admin menu collapsed/expanded state. * * Sets the global variable `menuState` and triggers a custom event passing * the current menu state. * * @since 4.7.0 * * @return {void} */ function setMenuState() { var viewportWidth = getViewportWidth() || 961; if ( viewportWidth <= 782 ) { menuState = 'responsive'; } else if ( $body.hasClass( 'folded' ) || ( $body.hasClass( 'auto-fold' ) && viewportWidth <= 960 && viewportWidth > 782 ) ) { menuState = 'folded'; } else { menuState = 'open'; } $document.trigger( 'wp-menu-state-set', { state: menuState } ); } // Set the menu state when the window gets resized. $document.on( 'wp-window-resized.set-menu-state', setMenuState ); /** * Sets ARIA attributes on the collapse/expand menu button. * * When the admin menu is open or folded, updates the `aria-expanded` and * `aria-label` attributes of the button to give feedback to assistive * technologies. In the responsive view, the button is always hidden. * * @since 4.7.0 * * @return {void} */ $document.on( 'wp-menu-state-set wp-collapse-menu', function( event, eventData ) { var $collapseButton = $( '#collapse-button' ), ariaExpanded, ariaLabelText; if ( 'folded' === eventData.state ) { ariaExpanded = 'false'; ariaLabelText = __( 'Expand Main menu' ); } else { ariaExpanded = 'true'; ariaLabelText = __( 'Collapse Main menu' ); } $collapseButton.attr({ 'aria-expanded': ariaExpanded, 'aria-label': ariaLabelText }); }); window.wpResponsive.init(); setPinMenu(); setMenuState(); makeNoticesDismissible(); aria_button_if_js(); $document.on( 'wp-pin-menu wp-window-resized.pin-menu postboxes-columnchange.pin-menu postbox-toggled.pin-menu wp-collapse-menu.pin-menu wp-scroll-start.pin-menu', setPinMenu ); // Set initial focus on a specific element. $( '.wp-initial-focus' ).trigger( 'focus' ); // Toggle update details on update-core.php. $body.on( 'click', '.js-update-details-toggle', function() { var $updateNotice = $( this ).closest( '.js-update-details' ), $progressDiv = $( '#' + $updateNotice.data( 'update-details' ) ); /* * When clicking on "Show details" move the progress div below the update * notice. Make sure it gets moved just the first time. */ if ( ! $progressDiv.hasClass( 'update-details-moved' ) ) { $progressDiv.insertAfter( $updateNotice ).addClass( 'update-details-moved' ); } // Toggle the progress div visibility. $progressDiv.toggle(); // Toggle the Show Details button expanded state. $( this ).attr( 'aria-expanded', $progressDiv.is( ':visible' ) ); }); }); /** * Hides the update button for expired plugin or theme uploads. * * On the "Update plugin/theme from uploaded zip" screen, once the upload has expired, * hides the "Replace current with uploaded" button and displays a warning. * * @since 5.5.0 */ $( function( $ ) { var $overwrite, $warning; if ( ! $body.hasClass( 'update-php' ) ) { return; } $overwrite = $( 'a.update-from-upload-overwrite' ); $warning = $( '.update-from-upload-expired' ); if ( ! $overwrite.length || ! $warning.length ) { return; } window.setTimeout( function() { $overwrite.hide(); $warning.removeClass( 'hidden' ); if ( window.wp && window.wp.a11y ) { window.wp.a11y.speak( $warning.text() ); } }, 7140000 // 119 minutes. The uploaded file is deleted after 2 hours. ); } ); // Fire a custom jQuery event at the end of window resize. ( function() { var timeout; /** * Triggers the WP window-resize event. * * @since 3.8.0 * * @return {void} */ function triggerEvent() { $document.trigger( 'wp-window-resized' ); } /** * Fires the trigger event again after 200 ms. * * @since 3.8.0 * * @return {void} */ function fireOnce() { window.clearTimeout( timeout ); timeout = window.setTimeout( triggerEvent, 200 ); } $window.on( 'resize.wp-fire-once', fireOnce ); }()); // Make Windows 8 devices play along nicely. (function(){ if ( '-ms-user-select' in document.documentElement.style && navigator.userAgent.match(/IEMobile\/10\.0/) ) { var msViewportStyle = document.createElement( 'style' ); msViewportStyle.appendChild( document.createTextNode( '@-ms-viewport{width:auto!important}' ) ); document.getElementsByTagName( 'head' )[0].appendChild( msViewportStyle ); } })(); }( jQuery, window )); /** * Freeze animated plugin icons when reduced motion is enabled. * * When the user has enabled the 'prefers-reduced-motion' setting, this module * stops animations for all GIFs on the page with the class 'plugin-icon' or * plugin icon images in the update plugins table. * * @since 6.4.0 */ (function() { // Private variables and methods. var priv = {}, pub = {}, mediaQuery; // Initialize pauseAll to false; it will be set to true if reduced motion is preferred. priv.pauseAll = false; if ( window.matchMedia ) { mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' ); if ( ! mediaQuery || mediaQuery.matches ) { priv.pauseAll = true; } } // Method to replace animated GIFs with a static frame. priv.freezeAnimatedPluginIcons = function( img ) { var coverImage = function() { var width = img.width; var height = img.height; var canvas = document.createElement( 'canvas' ); // Set canvas dimensions. canvas.width = width; canvas.height = height; // Copy classes from the image to the canvas. canvas.className = img.className; // Check if the image is inside a specific table. var isInsideUpdateTable = img.closest( '#update-plugins-table' ); if ( isInsideUpdateTable ) { // Transfer computed styles from image to canvas. var computedStyles = window.getComputedStyle( img ), i, max; for ( i = 0, max = computedStyles.length; i < max; i++ ) { var propName = computedStyles[ i ]; var propValue = computedStyles.getPropertyValue( propName ); canvas.style[ propName ] = propValue; } } // Draw the image onto the canvas. canvas.getContext( '2d' ).drawImage( img, 0, 0, width, height ); // Set accessibility attributes on canvas. canvas.setAttribute( 'aria-hidden', 'true' ); canvas.setAttribute( 'role', 'presentation' ); // Insert canvas before the image and set the image to be near-invisible. var parent = img.parentNode; parent.insertBefore( canvas, img ); img.style.opacity = 0.01; img.style.width = '0px'; img.style.height = '0px'; }; // If the image is already loaded, apply the coverImage function. if ( img.complete ) { coverImage(); } else { // Otherwise, wait for the image to load. img.addEventListener( 'load', coverImage, true ); } }; // Public method to freeze all relevant GIFs on the page. pub.freezeAll = function() { var images = document.querySelectorAll( '.plugin-icon, #update-plugins-table img' ); for ( var x = 0; x < images.length; x++ ) { if ( /\.gif(?:\?|$)/i.test( images[ x ].src ) ) { priv.freezeAnimatedPluginIcons( images[ x ] ); } } }; // Only run the freezeAll method if the user prefers reduced motion. if ( true === priv.pauseAll ) { pub.freezeAll(); } // Listen for jQuery AJAX events. ( function( $ ) { if ( window.pagenow === 'plugin-install' ) { // Only listen for ajaxComplete if this is the plugin-install.php page. $( document ).ajaxComplete( function( event, xhr, settings ) { // Check if this is the 'search-install-plugins' request. if ( settings.data && typeof settings.data === 'string' && settings.data.includes( 'action=search-install-plugins' ) ) { // Recheck if the user prefers reduced motion. if ( window.matchMedia ) { var mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' ); if ( mediaQuery.matches ) { pub.freezeAll(); } } else { // Fallback for browsers that don't support matchMedia. if ( true === priv.pauseAll ) { pub.freezeAll(); } } } } ); } } )( jQuery ); // Expose public methods. return pub; })(); user-suggest.js000064400000004375150251340050007543 0ustar00/** * Suggests users in a multisite environment. * * For input fields where the admin can select a user based on email or * username, this script shows an autocompletion menu for these inputs. Should * only be used in a multisite environment. Only users in the currently active * site are shown. * * @since 3.4.0 * @output wp-admin/js/user-suggest.js */ /* global ajaxurl, current_site_id, isRtl */ (function( $ ) { var id = ( typeof current_site_id !== 'undefined' ) ? '&site_id=' + current_site_id : ''; $( function() { var position = { offset: '0, -1' }; if ( typeof isRtl !== 'undefined' && isRtl ) { position.my = 'right top'; position.at = 'right bottom'; } /** * Adds an autocomplete function to input fields marked with the class * 'wp-suggest-user'. * * A minimum of two characters is required to trigger the suggestions. The * autocompletion menu is shown at the left bottom of the input field. On * RTL installations, it is shown at the right top. Adds the class 'open' to * the input field when the autocompletion menu is shown. * * Does a backend call to retrieve the users. * * Optional data-attributes: * - data-autocomplete-type (add, search) * The action that is going to be performed: search for existing users * or add a new one. Default: add * - data-autocomplete-field (user_login, user_email) * The field that is returned as the value for the suggestion. * Default: user_login * * @see wp-admin/includes/admin-actions.php:wp_ajax_autocomplete_user() */ $( '.wp-suggest-user' ).each( function(){ var $this = $( this ), autocompleteType = ( typeof $this.data( 'autocompleteType' ) !== 'undefined' ) ? $this.data( 'autocompleteType' ) : 'add', autocompleteField = ( typeof $this.data( 'autocompleteField' ) !== 'undefined' ) ? $this.data( 'autocompleteField' ) : 'user_login'; $this.autocomplete({ source: ajaxurl + '?action=autocomplete-user&autocomplete_type=' + autocompleteType + '&autocomplete_field=' + autocompleteField + id, delay: 500, minLength: 2, position: position, open: function() { $( this ).addClass( 'open' ); }, close: function() { $( this ).removeClass( 'open' ); } }); }); }); })( jQuery ); custom-background.min.js000064400000002266150251340050011314 0ustar00/*! This file is auto-generated */ !function(e){e(function(){var c,a=e("#custom-background-image");e("#background-color").wpColorPicker({change:function(n,o){a.css("background-color",o.color.toString())},clear:function(){a.css("background-color","")}}),e('select[name="background-size"]').on("change",function(){a.css("background-size",e(this).val())}),e('input[name="background-position"]').on("change",function(){a.css("background-position",e(this).val())}),e('input[name="background-repeat"]').on("change",function(){a.css("background-repeat",e(this).is(":checked")?"repeat":"no-repeat")}),e('input[name="background-attachment"]').on("change",function(){a.css("background-attachment",e(this).is(":checked")?"scroll":"fixed")}),e("#choose-from-library-link").on("click",function(n){var o=e(this);n.preventDefault(),c||(c=wp.media.frames.customBackground=wp.media({title:o.data("choose"),library:{type:"image"},button:{text:o.data("update"),close:!1}})).on("select",function(){var n=c.state().get("selection").first(),o=e("#_wpnonce").val()||"";e.post(ajaxurl,{action:"set-background-image",attachment_id:n.id,_ajax_nonce:o,size:"full"}).done(function(){window.location.reload()})}),c.open()})})}(jQuery);customize-nav-menus.js000064400000335663150251340050011046 0ustar00/** * @output wp-admin/js/customize-nav-menus.js */ /* global menus, _wpCustomizeNavMenusSettings, wpNavMenu, console */ ( function( api, wp, $ ) { 'use strict'; /** * Set up wpNavMenu for drag and drop. */ wpNavMenu.originalInit = wpNavMenu.init; wpNavMenu.options.menuItemDepthPerLevel = 20; wpNavMenu.options.sortableItems = '> .customize-control-nav_menu_item'; wpNavMenu.options.targetTolerance = 10; wpNavMenu.init = function() { this.jQueryExtensions(); }; /** * @namespace wp.customize.Menus */ api.Menus = api.Menus || {}; // Link settings. api.Menus.data = { itemTypes: [], l10n: {}, settingTransport: 'refresh', phpIntMax: 0, defaultSettingValues: { nav_menu: {}, nav_menu_item: {} }, locationSlugMappedToName: {} }; if ( 'undefined' !== typeof _wpCustomizeNavMenusSettings ) { $.extend( api.Menus.data, _wpCustomizeNavMenusSettings ); } /** * Newly-created Nav Menus and Nav Menu Items have negative integer IDs which * serve as placeholders until Save & Publish happens. * * @alias wp.customize.Menus.generatePlaceholderAutoIncrementId * * @return {number} */ api.Menus.generatePlaceholderAutoIncrementId = function() { return -Math.ceil( api.Menus.data.phpIntMax * Math.random() ); }; /** * wp.customize.Menus.AvailableItemModel * * A single available menu item model. See PHP's WP_Customize_Nav_Menu_Item_Setting class. * * @class wp.customize.Menus.AvailableItemModel * @augments Backbone.Model */ api.Menus.AvailableItemModel = Backbone.Model.extend( $.extend( { id: null // This is only used by Backbone. }, api.Menus.data.defaultSettingValues.nav_menu_item ) ); /** * wp.customize.Menus.AvailableItemCollection * * Collection for available menu item models. * * @class wp.customize.Menus.AvailableItemCollection * @augments Backbone.Collection */ api.Menus.AvailableItemCollection = Backbone.Collection.extend(/** @lends wp.customize.Menus.AvailableItemCollection.prototype */{ model: api.Menus.AvailableItemModel, sort_key: 'order', comparator: function( item ) { return -item.get( this.sort_key ); }, sortByField: function( fieldName ) { this.sort_key = fieldName; this.sort(); } }); api.Menus.availableMenuItems = new api.Menus.AvailableItemCollection( api.Menus.data.availableMenuItems ); /** * Insert a new `auto-draft` post. * * @since 4.7.0 * @alias wp.customize.Menus.insertAutoDraftPost * * @param {Object} params - Parameters for the draft post to create. * @param {string} params.post_type - Post type to add. * @param {string} params.post_title - Post title to use. * @return {jQuery.promise} Promise resolved with the added post. */ api.Menus.insertAutoDraftPost = function insertAutoDraftPost( params ) { var request, deferred = $.Deferred(); request = wp.ajax.post( 'customize-nav-menus-insert-auto-draft', { 'customize-menus-nonce': api.settings.nonce['customize-menus'], 'wp_customize': 'on', 'customize_changeset_uuid': api.settings.changeset.uuid, 'params': params } ); request.done( function( response ) { if ( response.post_id ) { api( 'nav_menus_created_posts' ).set( api( 'nav_menus_created_posts' ).get().concat( [ response.post_id ] ) ); if ( 'page' === params.post_type ) { // Activate static front page controls as this could be the first page created. if ( api.section.has( 'static_front_page' ) ) { api.section( 'static_front_page' ).activate(); } // Add new page to dropdown-pages controls. api.control.each( function( control ) { var select; if ( 'dropdown-pages' === control.params.type ) { select = control.container.find( 'select[name^="_customize-dropdown-pages-"]' ); select.append( new Option( params.post_title, response.post_id ) ); } } ); } deferred.resolve( response ); } } ); request.fail( function( response ) { var error = response || ''; if ( 'undefined' !== typeof response.message ) { error = response.message; } console.error( error ); deferred.rejectWith( error ); } ); return deferred.promise(); }; api.Menus.AvailableMenuItemsPanelView = wp.Backbone.View.extend(/** @lends wp.customize.Menus.AvailableMenuItemsPanelView.prototype */{ el: '#available-menu-items', events: { 'input #menu-items-search': 'debounceSearch', 'focus .menu-item-tpl': 'focus', 'click .menu-item-tpl': '_submit', 'click #custom-menu-item-submit': '_submitLink', 'keypress #custom-menu-item-name': '_submitLink', 'click .new-content-item .add-content': '_submitNew', 'keypress .create-item-input': '_submitNew', 'keydown': 'keyboardAccessible' }, // Cache current selected menu item. selected: null, // Cache menu control that opened the panel. currentMenuControl: null, debounceSearch: null, $search: null, $clearResults: null, searchTerm: '', rendered: false, pages: {}, sectionContent: '', loading: false, addingNew: false, /** * wp.customize.Menus.AvailableMenuItemsPanelView * * View class for the available menu items panel. * * @constructs wp.customize.Menus.AvailableMenuItemsPanelView * @augments wp.Backbone.View */ initialize: function() { var self = this; if ( ! api.panel.has( 'nav_menus' ) ) { return; } this.$search = $( '#menu-items-search' ); this.$clearResults = this.$el.find( '.clear-results' ); this.sectionContent = this.$el.find( '.available-menu-items-list' ); this.debounceSearch = _.debounce( self.search, 500 ); _.bindAll( this, 'close' ); /* * If the available menu items panel is open and the customize controls * are interacted with (other than an item being deleted), then close * the available menu items panel. Also close on back button click. */ $( '#customize-controls, .customize-section-back' ).on( 'click keydown', function( e ) { var isDeleteBtn = $( e.target ).is( '.item-delete, .item-delete *' ), isAddNewBtn = $( e.target ).is( '.add-new-menu-item, .add-new-menu-item *' ); if ( $( 'body' ).hasClass( 'adding-menu-items' ) && ! isDeleteBtn && ! isAddNewBtn ) { self.close(); } } ); // Clear the search results and trigger an `input` event to fire a new search. this.$clearResults.on( 'click', function() { self.$search.val( '' ).trigger( 'focus' ).trigger( 'input' ); } ); this.$el.on( 'input', '#custom-menu-item-name.invalid, #custom-menu-item-url.invalid', function() { $( this ).removeClass( 'invalid' ); var errorMessageId = $( this ).attr( 'aria-describedby' ); $( '#' + errorMessageId ).hide(); $( this ).removeAttr( 'aria-invalid' ).removeAttr( 'aria-describedby' ); }); // Load available items if it looks like we'll need them. api.panel( 'nav_menus' ).container.on( 'expanded', function() { if ( ! self.rendered ) { self.initList(); self.rendered = true; } }); // Load more items. this.sectionContent.on( 'scroll', function() { var totalHeight = self.$el.find( '.accordion-section.open .available-menu-items-list' ).prop( 'scrollHeight' ), visibleHeight = self.$el.find( '.accordion-section.open' ).height(); if ( ! self.loading && $( this ).scrollTop() > 3 / 4 * totalHeight - visibleHeight ) { var type = $( this ).data( 'type' ), object = $( this ).data( 'object' ); if ( 'search' === type ) { if ( self.searchTerm ) { self.doSearch( self.pages.search ); } } else { self.loadItems( [ { type: type, object: object } ] ); } } }); // Close the panel if the URL in the preview changes. api.previewer.bind( 'url', this.close ); self.delegateEvents(); }, // Search input change handler. search: function( event ) { var $searchSection = $( '#available-menu-items-search' ), $otherSections = $( '#available-menu-items .accordion-section' ).not( $searchSection ); if ( ! event ) { return; } if ( this.searchTerm === event.target.value ) { return; } if ( '' !== event.target.value && ! $searchSection.hasClass( 'open' ) ) { $otherSections.fadeOut( 100 ); $searchSection.find( '.accordion-section-content' ).slideDown( 'fast' ); $searchSection.addClass( 'open' ); this.$clearResults.addClass( 'is-visible' ); } else if ( '' === event.target.value ) { $searchSection.removeClass( 'open' ); $otherSections.show(); this.$clearResults.removeClass( 'is-visible' ); } this.searchTerm = event.target.value; this.pages.search = 1; this.doSearch( 1 ); }, // Get search results. doSearch: function( page ) { var self = this, params, $section = $( '#available-menu-items-search' ), $content = $section.find( '.accordion-section-content' ), itemTemplate = wp.template( 'available-menu-item' ); if ( self.currentRequest ) { self.currentRequest.abort(); } if ( page < 0 ) { return; } else if ( page > 1 ) { $section.addClass( 'loading-more' ); $content.attr( 'aria-busy', 'true' ); wp.a11y.speak( api.Menus.data.l10n.itemsLoadingMore ); } else if ( '' === self.searchTerm ) { $content.html( '' ); wp.a11y.speak( '' ); return; } $section.addClass( 'loading' ); self.loading = true; params = api.previewer.query( { excludeCustomizedSaved: true } ); _.extend( params, { 'customize-menus-nonce': api.settings.nonce['customize-menus'], 'wp_customize': 'on', 'search': self.searchTerm, 'page': page } ); self.currentRequest = wp.ajax.post( 'search-available-menu-items-customizer', params ); self.currentRequest.done(function( data ) { var items; if ( 1 === page ) { // Clear previous results as it's a new search. $content.empty(); } $section.removeClass( 'loading loading-more' ); $content.attr( 'aria-busy', 'false' ); $section.addClass( 'open' ); self.loading = false; items = new api.Menus.AvailableItemCollection( data.items ); self.collection.add( items.models ); items.each( function( menuItem ) { $content.append( itemTemplate( menuItem.attributes ) ); } ); if ( 20 > items.length ) { self.pages.search = -1; // Up to 20 posts and 20 terms in results, if <20, no more results for either. } else { self.pages.search = self.pages.search + 1; } if ( items && page > 1 ) { wp.a11y.speak( api.Menus.data.l10n.itemsFoundMore.replace( '%d', items.length ) ); } else if ( items && page === 1 ) { wp.a11y.speak( api.Menus.data.l10n.itemsFound.replace( '%d', items.length ) ); } }); self.currentRequest.fail(function( data ) { // data.message may be undefined, for example when typing slow and the request is aborted. if ( data.message ) { $content.empty().append( $( '
  • ' ).text( data.message ) ); wp.a11y.speak( data.message ); } self.pages.search = -1; }); self.currentRequest.always(function() { $section.removeClass( 'loading loading-more' ); $content.attr( 'aria-busy', 'false' ); self.loading = false; self.currentRequest = null; }); }, // Render the individual items. initList: function() { var self = this; // Render the template for each item by type. _.each( api.Menus.data.itemTypes, function( itemType ) { self.pages[ itemType.type + ':' + itemType.object ] = 0; } ); self.loadItems( api.Menus.data.itemTypes ); }, /** * Load available nav menu items. * * @since 4.3.0 * @since 4.7.0 Changed function signature to take list of item types instead of single type/object. * @access private * * @param {Array.} itemTypes List of objects containing type and key. * @param {string} deprecated Formerly the object parameter. * @return {void} */ loadItems: function( itemTypes, deprecated ) { var self = this, _itemTypes, requestItemTypes = [], params, request, itemTemplate, availableMenuItemContainers = {}; itemTemplate = wp.template( 'available-menu-item' ); if ( _.isString( itemTypes ) && _.isString( deprecated ) ) { _itemTypes = [ { type: itemTypes, object: deprecated } ]; } else { _itemTypes = itemTypes; } _.each( _itemTypes, function( itemType ) { var container, name = itemType.type + ':' + itemType.object; if ( -1 === self.pages[ name ] ) { return; // Skip types for which there are no more results. } container = $( '#available-menu-items-' + itemType.type + '-' + itemType.object ); container.find( '.accordion-section-title' ).addClass( 'loading' ); availableMenuItemContainers[ name ] = container; requestItemTypes.push( { object: itemType.object, type: itemType.type, page: self.pages[ name ] } ); } ); if ( 0 === requestItemTypes.length ) { return; } self.loading = true; params = api.previewer.query( { excludeCustomizedSaved: true } ); _.extend( params, { 'customize-menus-nonce': api.settings.nonce['customize-menus'], 'wp_customize': 'on', 'item_types': requestItemTypes } ); request = wp.ajax.post( 'load-available-menu-items-customizer', params ); request.done(function( data ) { var typeInner; _.each( data.items, function( typeItems, name ) { if ( 0 === typeItems.length ) { if ( 0 === self.pages[ name ] ) { availableMenuItemContainers[ name ].find( '.accordion-section-title' ) .addClass( 'cannot-expand' ) .removeClass( 'loading' ) .find( '.accordion-section-title > button' ) .prop( 'tabIndex', -1 ); } self.pages[ name ] = -1; return; } else if ( ( 'post_type:page' === name ) && ( ! availableMenuItemContainers[ name ].hasClass( 'open' ) ) ) { availableMenuItemContainers[ name ].find( '.accordion-section-title > button' ).trigger( 'click' ); } typeItems = new api.Menus.AvailableItemCollection( typeItems ); // @todo Why is this collection created and then thrown away? self.collection.add( typeItems.models ); typeInner = availableMenuItemContainers[ name ].find( '.available-menu-items-list' ); typeItems.each( function( menuItem ) { typeInner.append( itemTemplate( menuItem.attributes ) ); } ); self.pages[ name ] += 1; }); }); request.fail(function( data ) { if ( typeof console !== 'undefined' && console.error ) { console.error( data ); } }); request.always(function() { _.each( availableMenuItemContainers, function( container ) { container.find( '.accordion-section-title' ).removeClass( 'loading' ); } ); self.loading = false; }); }, // Adjust the height of each section of items to fit the screen. itemSectionHeight: function() { var sections, lists, totalHeight, accordionHeight, diff; totalHeight = window.innerHeight; sections = this.$el.find( '.accordion-section:not( #available-menu-items-search ) .accordion-section-content' ); lists = this.$el.find( '.accordion-section:not( #available-menu-items-search ) .available-menu-items-list:not(":only-child")' ); accordionHeight = 46 * ( 1 + sections.length ) + 14; // Magic numbers. diff = totalHeight - accordionHeight; if ( 120 < diff && 290 > diff ) { sections.css( 'max-height', diff ); lists.css( 'max-height', ( diff - 60 ) ); } }, // Highlights a menu item. select: function( menuitemTpl ) { this.selected = $( menuitemTpl ); this.selected.siblings( '.menu-item-tpl' ).removeClass( 'selected' ); this.selected.addClass( 'selected' ); }, // Highlights a menu item on focus. focus: function( event ) { this.select( $( event.currentTarget ) ); }, // Submit handler for keypress and click on menu item. _submit: function( event ) { // Only proceed with keypress if it is Enter or Spacebar. if ( 'keypress' === event.type && ( 13 !== event.which && 32 !== event.which ) ) { return; } this.submit( $( event.currentTarget ) ); }, // Adds a selected menu item to the menu. submit: function( menuitemTpl ) { var menuitemId, menu_item; if ( ! menuitemTpl ) { menuitemTpl = this.selected; } if ( ! menuitemTpl || ! this.currentMenuControl ) { return; } this.select( menuitemTpl ); menuitemId = $( this.selected ).data( 'menu-item-id' ); menu_item = this.collection.findWhere( { id: menuitemId } ); if ( ! menu_item ) { return; } this.currentMenuControl.addItemToMenu( menu_item.attributes ); $( menuitemTpl ).find( '.menu-item-handle' ).addClass( 'item-added' ); }, // Submit handler for keypress and click on custom menu item. _submitLink: function( event ) { // Only proceed with keypress if it is Enter. if ( 'keypress' === event.type && 13 !== event.which ) { return; } this.submitLink(); }, // Adds the custom menu item to the menu. submitLink: function() { var menuItem, itemName = $( '#custom-menu-item-name' ), itemUrl = $( '#custom-menu-item-url' ), urlErrorMessage = $( '#custom-url-error' ), nameErrorMessage = $( '#custom-name-error' ), url = itemUrl.val().trim(), urlRegex, errorText; if ( ! this.currentMenuControl ) { return; } /* * Allow URLs including: * - http://example.com/ * - //example.com * - /directory/ * - ?query-param * - #target * - mailto:foo@example.com * * Any further validation will be handled on the server when the setting is attempted to be saved, * so this pattern does not need to be complete. */ urlRegex = /^((\w+:)?\/\/\w.*|\w+:(?!\/\/$)|\/|\?|#)/; if ( ! urlRegex.test( url ) || '' === itemName.val() ) { if ( ! urlRegex.test( url ) ) { itemUrl.addClass( 'invalid' ) .attr( 'aria-invalid', 'true' ) .attr( 'aria-describedby', 'custom-url-error' ); urlErrorMessage.show(); errorText = urlErrorMessage.text(); // Announce error message via screen reader wp.a11y.speak( errorText, 'assertive' ); } if ( '' === itemName.val() ) { itemName.addClass( 'invalid' ) .attr( 'aria-invalid', 'true' ) .attr( 'aria-describedby', 'custom-name-error' ); nameErrorMessage.show(); errorText = ( '' === errorText ) ? nameErrorMessage.text() : errorText + nameErrorMessage.text(); // Announce error message via screen reader wp.a11y.speak( errorText, 'assertive' ); } return; } urlErrorMessage.hide(); nameErrorMessage.hide(); itemName.removeClass( 'invalid' ) .removeAttr( 'aria-invalid', 'true' ) .removeAttr( 'aria-describedby', 'custom-name-error' ); itemUrl.removeClass( 'invalid' ) .removeAttr( 'aria-invalid', 'true' ) .removeAttr( 'aria-describedby', 'custom-name-error' ); menuItem = { 'title': itemName.val(), 'url': url, 'type': 'custom', 'type_label': api.Menus.data.l10n.custom_label, 'object': 'custom' }; this.currentMenuControl.addItemToMenu( menuItem ); // Reset the custom link form. itemUrl.val( '' ).attr( 'placeholder', 'https://' ); itemName.val( '' ); }, /** * Submit handler for keypress (enter) on field and click on button. * * @since 4.7.0 * @private * * @param {jQuery.Event} event Event. * @return {void} */ _submitNew: function( event ) { var container; // Only proceed with keypress if it is Enter. if ( 'keypress' === event.type && 13 !== event.which ) { return; } if ( this.addingNew ) { return; } container = $( event.target ).closest( '.accordion-section' ); this.submitNew( container ); }, /** * Creates a new object and adds an associated menu item to the menu. * * @since 4.7.0 * @private * * @param {jQuery} container * @return {void} */ submitNew: function( container ) { var panel = this, itemName = container.find( '.create-item-input' ), title = itemName.val(), dataContainer = container.find( '.available-menu-items-list' ), itemType = dataContainer.data( 'type' ), itemObject = dataContainer.data( 'object' ), itemTypeLabel = dataContainer.data( 'type_label' ), promise; if ( ! this.currentMenuControl ) { return; } // Only posts are supported currently. if ( 'post_type' !== itemType ) { return; } if ( '' === itemName.val().trim() ) { itemName.addClass( 'invalid' ); itemName.focus(); return; } else { itemName.removeClass( 'invalid' ); container.find( '.accordion-section-title' ).addClass( 'loading' ); } panel.addingNew = true; itemName.attr( 'disabled', 'disabled' ); promise = api.Menus.insertAutoDraftPost( { post_title: title, post_type: itemObject } ); promise.done( function( data ) { var availableItem, $content, itemElement; availableItem = new api.Menus.AvailableItemModel( { 'id': 'post-' + data.post_id, // Used for available menu item Backbone models. 'title': itemName.val(), 'type': itemType, 'type_label': itemTypeLabel, 'object': itemObject, 'object_id': data.post_id, 'url': data.url } ); // Add new item to menu. panel.currentMenuControl.addItemToMenu( availableItem.attributes ); // Add the new item to the list of available items. api.Menus.availableMenuItemsPanel.collection.add( availableItem ); $content = container.find( '.available-menu-items-list' ); itemElement = $( wp.template( 'available-menu-item' )( availableItem.attributes ) ); itemElement.find( '.menu-item-handle:first' ).addClass( 'item-added' ); $content.prepend( itemElement ); $content.scrollTop(); // Reset the create content form. itemName.val( '' ).removeAttr( 'disabled' ); panel.addingNew = false; container.find( '.accordion-section-title' ).removeClass( 'loading' ); } ); }, // Opens the panel. open: function( menuControl ) { var panel = this, close; this.currentMenuControl = menuControl; this.itemSectionHeight(); if ( api.section.has( 'publish_settings' ) ) { api.section( 'publish_settings' ).collapse(); } $( 'body' ).addClass( 'adding-menu-items' ); close = function() { panel.close(); $( this ).off( 'click', close ); }; $( '#customize-preview' ).on( 'click', close ); // Collapse all controls. _( this.currentMenuControl.getMenuItemControls() ).each( function( control ) { control.collapseForm(); } ); this.$el.find( '.selected' ).removeClass( 'selected' ); this.$search.trigger( 'focus' ); }, // Closes the panel. close: function( options ) { options = options || {}; if ( options.returnFocus && this.currentMenuControl ) { this.currentMenuControl.container.find( '.add-new-menu-item' ).focus(); } this.currentMenuControl = null; this.selected = null; $( 'body' ).removeClass( 'adding-menu-items' ); $( '#available-menu-items .menu-item-handle.item-added' ).removeClass( 'item-added' ); this.$search.val( '' ).trigger( 'input' ); }, // Add a few keyboard enhancements to the panel. keyboardAccessible: function( event ) { var isEnter = ( 13 === event.which ), isEsc = ( 27 === event.which ), isBackTab = ( 9 === event.which && event.shiftKey ), isSearchFocused = $( event.target ).is( this.$search ); // If enter pressed but nothing entered, don't do anything. if ( isEnter && ! this.$search.val() ) { return; } if ( isSearchFocused && isBackTab ) { this.currentMenuControl.container.find( '.add-new-menu-item' ).focus(); event.preventDefault(); // Avoid additional back-tab. } else if ( isEsc ) { this.close( { returnFocus: true } ); } } }); /** * wp.customize.Menus.MenusPanel * * Customizer panel for menus. This is used only for screen options management. * Note that 'menus' must match the WP_Customize_Menu_Panel::$type. * * @class wp.customize.Menus.MenusPanel * @augments wp.customize.Panel */ api.Menus.MenusPanel = api.Panel.extend(/** @lends wp.customize.Menus.MenusPanel.prototype */{ attachEvents: function() { api.Panel.prototype.attachEvents.call( this ); var panel = this, panelMeta = panel.container.find( '.panel-meta' ), help = panelMeta.find( '.customize-help-toggle' ), content = panelMeta.find( '.customize-panel-description' ), options = $( '#screen-options-wrap' ), button = panelMeta.find( '.customize-screen-options-toggle' ); button.on( 'click keydown', function( event ) { if ( api.utils.isKeydownButNotEnterEvent( event ) ) { return; } event.preventDefault(); // Hide description. if ( content.not( ':hidden' ) ) { content.slideUp( 'fast' ); help.attr( 'aria-expanded', 'false' ); } if ( 'true' === button.attr( 'aria-expanded' ) ) { button.attr( 'aria-expanded', 'false' ); panelMeta.removeClass( 'open' ); panelMeta.removeClass( 'active-menu-screen-options' ); options.slideUp( 'fast' ); } else { button.attr( 'aria-expanded', 'true' ); panelMeta.addClass( 'open' ); panelMeta.addClass( 'active-menu-screen-options' ); options.slideDown( 'fast' ); } return false; } ); // Help toggle. help.on( 'click keydown', function( event ) { if ( api.utils.isKeydownButNotEnterEvent( event ) ) { return; } event.preventDefault(); if ( 'true' === button.attr( 'aria-expanded' ) ) { button.attr( 'aria-expanded', 'false' ); help.attr( 'aria-expanded', 'true' ); panelMeta.addClass( 'open' ); panelMeta.removeClass( 'active-menu-screen-options' ); options.slideUp( 'fast' ); content.slideDown( 'fast' ); } } ); }, /** * Update field visibility when clicking on the field toggles. */ ready: function() { var panel = this; panel.container.find( '.hide-column-tog' ).on( 'click', function() { panel.saveManageColumnsState(); }); // Inject additional heading into the menu locations section's head container. api.section( 'menu_locations', function( section ) { section.headContainer.prepend( wp.template( 'nav-menu-locations-header' )( api.Menus.data ) ); } ); }, /** * Save hidden column states. * * @since 4.3.0 * @private * * @return {void} */ saveManageColumnsState: _.debounce( function() { var panel = this; if ( panel._updateHiddenColumnsRequest ) { panel._updateHiddenColumnsRequest.abort(); } panel._updateHiddenColumnsRequest = wp.ajax.post( 'hidden-columns', { hidden: panel.hidden(), screenoptionnonce: $( '#screenoptionnonce' ).val(), page: 'nav-menus' } ); panel._updateHiddenColumnsRequest.always( function() { panel._updateHiddenColumnsRequest = null; } ); }, 2000 ), /** * @deprecated Since 4.7.0 now that the nav_menu sections are responsible for toggling the classes on their own containers. */ checked: function() {}, /** * @deprecated Since 4.7.0 now that the nav_menu sections are responsible for toggling the classes on their own containers. */ unchecked: function() {}, /** * Get hidden fields. * * @since 4.3.0 * @private * * @return {Array} Fields (columns) that are hidden. */ hidden: function() { return $( '.hide-column-tog' ).not( ':checked' ).map( function() { var id = this.id; return id.substring( 0, id.length - 5 ); }).get().join( ',' ); } } ); /** * wp.customize.Menus.MenuSection * * Customizer section for menus. This is used only for lazy-loading child controls. * Note that 'nav_menu' must match the WP_Customize_Menu_Section::$type. * * @class wp.customize.Menus.MenuSection * @augments wp.customize.Section */ api.Menus.MenuSection = api.Section.extend(/** @lends wp.customize.Menus.MenuSection.prototype */{ /** * Initialize. * * @since 4.3.0 * * @param {string} id * @param {Object} options */ initialize: function( id, options ) { var section = this; api.Section.prototype.initialize.call( section, id, options ); section.deferred.initSortables = $.Deferred(); }, /** * Ready. */ ready: function() { var section = this, fieldActiveToggles, handleFieldActiveToggle; if ( 'undefined' === typeof section.params.menu_id ) { throw new Error( 'params.menu_id was not defined' ); } /* * Since newly created sections won't be registered in PHP, we need to prevent the * preview's sending of the activeSections to result in this control * being deactivated when the preview refreshes. So we can hook onto * the setting that has the same ID and its presence can dictate * whether the section is active. */ section.active.validate = function() { if ( ! api.has( section.id ) ) { return false; } return !! api( section.id ).get(); }; section.populateControls(); section.navMenuLocationSettings = {}; section.assignedLocations = new api.Value( [] ); api.each(function( setting, id ) { var matches = id.match( /^nav_menu_locations\[(.+?)]/ ); if ( matches ) { section.navMenuLocationSettings[ matches[1] ] = setting; setting.bind( function() { section.refreshAssignedLocations(); }); } }); section.assignedLocations.bind(function( to ) { section.updateAssignedLocationsInSectionTitle( to ); }); section.refreshAssignedLocations(); api.bind( 'pane-contents-reflowed', function() { // Skip menus that have been removed. if ( ! section.contentContainer.parent().length ) { return; } section.container.find( '.menu-item .menu-item-reorder-nav button' ).attr({ 'tabindex': '0', 'aria-hidden': 'false' }); section.container.find( '.menu-item.move-up-disabled .menus-move-up' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' }); section.container.find( '.menu-item.move-down-disabled .menus-move-down' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' }); section.container.find( '.menu-item.move-left-disabled .menus-move-left' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' }); section.container.find( '.menu-item.move-right-disabled .menus-move-right' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' }); } ); /** * Update the active field class for the content container for a given checkbox toggle. * * @this {jQuery} * @return {void} */ handleFieldActiveToggle = function() { var className = 'field-' + $( this ).val() + '-active'; section.contentContainer.toggleClass( className, $( this ).prop( 'checked' ) ); }; fieldActiveToggles = api.panel( 'nav_menus' ).contentContainer.find( '.metabox-prefs:first' ).find( '.hide-column-tog' ); fieldActiveToggles.each( handleFieldActiveToggle ); fieldActiveToggles.on( 'click', handleFieldActiveToggle ); }, populateControls: function() { var section = this, menuNameControlId, menuLocationsControlId, menuAutoAddControlId, menuDeleteControlId, menuControl, menuNameControl, menuLocationsControl, menuAutoAddControl, menuDeleteControl; // Add the control for managing the menu name. menuNameControlId = section.id + '[name]'; menuNameControl = api.control( menuNameControlId ); if ( ! menuNameControl ) { menuNameControl = new api.controlConstructor.nav_menu_name( menuNameControlId, { type: 'nav_menu_name', label: api.Menus.data.l10n.menuNameLabel, section: section.id, priority: 0, settings: { 'default': section.id } } ); api.control.add( menuNameControl ); menuNameControl.active.set( true ); } // Add the menu control. menuControl = api.control( section.id ); if ( ! menuControl ) { menuControl = new api.controlConstructor.nav_menu( section.id, { type: 'nav_menu', section: section.id, priority: 998, settings: { 'default': section.id }, menu_id: section.params.menu_id } ); api.control.add( menuControl ); menuControl.active.set( true ); } // Add the menu locations control. menuLocationsControlId = section.id + '[locations]'; menuLocationsControl = api.control( menuLocationsControlId ); if ( ! menuLocationsControl ) { menuLocationsControl = new api.controlConstructor.nav_menu_locations( menuLocationsControlId, { section: section.id, priority: 999, settings: { 'default': section.id }, menu_id: section.params.menu_id } ); api.control.add( menuLocationsControl.id, menuLocationsControl ); menuControl.active.set( true ); } // Add the control for managing the menu auto_add. menuAutoAddControlId = section.id + '[auto_add]'; menuAutoAddControl = api.control( menuAutoAddControlId ); if ( ! menuAutoAddControl ) { menuAutoAddControl = new api.controlConstructor.nav_menu_auto_add( menuAutoAddControlId, { type: 'nav_menu_auto_add', label: '', section: section.id, priority: 1000, settings: { 'default': section.id } } ); api.control.add( menuAutoAddControl ); menuAutoAddControl.active.set( true ); } // Add the control for deleting the menu. menuDeleteControlId = section.id + '[delete]'; menuDeleteControl = api.control( menuDeleteControlId ); if ( ! menuDeleteControl ) { menuDeleteControl = new api.Control( menuDeleteControlId, { section: section.id, priority: 1001, templateId: 'nav-menu-delete-button' } ); api.control.add( menuDeleteControl.id, menuDeleteControl ); menuDeleteControl.active.set( true ); menuDeleteControl.deferred.embedded.done( function () { menuDeleteControl.container.find( 'button' ).on( 'click', function() { var menuId = section.params.menu_id; var menuControl = api.Menus.getMenuControl( menuId ); menuControl.setting.set( false ); }); } ); } }, /** * */ refreshAssignedLocations: function() { var section = this, menuTermId = section.params.menu_id, currentAssignedLocations = []; _.each( section.navMenuLocationSettings, function( setting, themeLocation ) { if ( setting() === menuTermId ) { currentAssignedLocations.push( themeLocation ); } }); section.assignedLocations.set( currentAssignedLocations ); }, /** * @param {Array} themeLocationSlugs Theme location slugs. */ updateAssignedLocationsInSectionTitle: function( themeLocationSlugs ) { var section = this, $title; $title = section.container.find( '.accordion-section-title button:first' ); $title.find( '.menu-in-location' ).remove(); _.each( themeLocationSlugs, function( themeLocationSlug ) { var $label, locationName; $label = $( '' ); locationName = api.Menus.data.locationSlugMappedToName[ themeLocationSlug ]; $label.text( api.Menus.data.l10n.menuLocation.replace( '%s', locationName ) ); $title.append( $label ); }); section.container.toggleClass( 'assigned-to-menu-location', 0 !== themeLocationSlugs.length ); }, onChangeExpanded: function( expanded, args ) { var section = this, completeCallback; if ( expanded ) { wpNavMenu.menuList = section.contentContainer; wpNavMenu.targetList = wpNavMenu.menuList; // Add attributes needed by wpNavMenu. $( '#menu-to-edit' ).removeAttr( 'id' ); wpNavMenu.menuList.attr( 'id', 'menu-to-edit' ).addClass( 'menu' ); api.Menus.MenuItemControl.prototype.initAccessibility(); _.each( api.section( section.id ).controls(), function( control ) { if ( 'nav_menu_item' === control.params.type ) { control.actuallyEmbed(); } } ); // Make sure Sortables is initialized after the section has been expanded to prevent `offset` issues. if ( args.completeCallback ) { completeCallback = args.completeCallback; } args.completeCallback = function() { if ( 'resolved' !== section.deferred.initSortables.state() ) { wpNavMenu.initSortables(); // Depends on menu-to-edit ID being set above. section.deferred.initSortables.resolve( wpNavMenu.menuList ); // Now MenuControl can extend the sortable. // @todo Note that wp.customize.reflowPaneContents() is debounced, // so this immediate change will show a slight flicker while priorities get updated. api.control( 'nav_menu[' + String( section.params.menu_id ) + ']' ).reflowMenuItems(); } if ( _.isFunction( completeCallback ) ) { completeCallback(); } }; } api.Section.prototype.onChangeExpanded.call( section, expanded, args ); }, /** * Highlight how a user may create new menu items. * * This method reminds the user to create new menu items and how. * It's exposed this way because this class knows best which UI needs * highlighted but those expanding this section know more about why and * when the affordance should be highlighted. * * @since 4.9.0 * * @return {void} */ highlightNewItemButton: function() { api.utils.highlightButton( this.contentContainer.find( '.add-new-menu-item' ), { delay: 2000 } ); } }); /** * Create a nav menu setting and section. * * @since 4.9.0 * * @param {string} [name=''] Nav menu name. * @return {wp.customize.Menus.MenuSection} Added nav menu. */ api.Menus.createNavMenu = function createNavMenu( name ) { var customizeId, placeholderId, setting; placeholderId = api.Menus.generatePlaceholderAutoIncrementId(); customizeId = 'nav_menu[' + String( placeholderId ) + ']'; // Register the menu control setting. setting = api.create( customizeId, customizeId, {}, { type: 'nav_menu', transport: api.Menus.data.settingTransport, previewer: api.previewer } ); setting.set( $.extend( {}, api.Menus.data.defaultSettingValues.nav_menu, { name: name || '' } ) ); /* * Add the menu section (and its controls). * Note that this will automatically create the required controls * inside via the Section's ready method. */ return api.section.add( new api.Menus.MenuSection( customizeId, { panel: 'nav_menus', title: displayNavMenuName( name ), customizeAction: api.Menus.data.l10n.customizingMenus, priority: 10, menu_id: placeholderId } ) ); }; /** * wp.customize.Menus.NewMenuSection * * Customizer section for new menus. * * @class wp.customize.Menus.NewMenuSection * @augments wp.customize.Section */ api.Menus.NewMenuSection = api.Section.extend(/** @lends wp.customize.Menus.NewMenuSection.prototype */{ /** * Add behaviors for the accordion section. * * @since 4.3.0 */ attachEvents: function() { var section = this, container = section.container, contentContainer = section.contentContainer, navMenuSettingPattern = /^nav_menu\[/; section.headContainer.find( '.accordion-section-title' ).replaceWith( wp.template( 'nav-menu-create-menu-section-title' ) ); /* * We have to manually handle section expanded because we do not * apply the `accordion-section-title` class to this button-driven section. */ container.on( 'click', '.customize-add-menu-button', function() { section.expand(); }); contentContainer.on( 'keydown', '.menu-name-field', function( event ) { if ( 13 === event.which ) { // Enter. section.submit(); } } ); contentContainer.on( 'click', '#customize-new-menu-submit', function( event ) { section.submit(); event.stopPropagation(); event.preventDefault(); } ); /** * Get number of non-deleted nav menus. * * @since 4.9.0 * @return {number} Count. */ function getNavMenuCount() { var count = 0; api.each( function( setting ) { if ( navMenuSettingPattern.test( setting.id ) && false !== setting.get() ) { count += 1; } } ); return count; } /** * Update visibility of notice to prompt users to create menus. * * @since 4.9.0 * @return {void} */ function updateNoticeVisibility() { container.find( '.add-new-menu-notice' ).prop( 'hidden', getNavMenuCount() > 0 ); } /** * Handle setting addition. * * @since 4.9.0 * @param {wp.customize.Setting} setting - Added setting. * @return {void} */ function addChangeEventListener( setting ) { if ( navMenuSettingPattern.test( setting.id ) ) { setting.bind( updateNoticeVisibility ); updateNoticeVisibility(); } } /** * Handle setting removal. * * @since 4.9.0 * @param {wp.customize.Setting} setting - Removed setting. * @return {void} */ function removeChangeEventListener( setting ) { if ( navMenuSettingPattern.test( setting.id ) ) { setting.unbind( updateNoticeVisibility ); updateNoticeVisibility(); } } api.each( addChangeEventListener ); api.bind( 'add', addChangeEventListener ); api.bind( 'removed', removeChangeEventListener ); updateNoticeVisibility(); api.Section.prototype.attachEvents.apply( section, arguments ); }, /** * Set up the control. * * @since 4.9.0 */ ready: function() { this.populateControls(); }, /** * Create the controls for this section. * * @since 4.9.0 */ populateControls: function() { var section = this, menuNameControlId, menuLocationsControlId, newMenuSubmitControlId, menuNameControl, menuLocationsControl, newMenuSubmitControl; menuNameControlId = section.id + '[name]'; menuNameControl = api.control( menuNameControlId ); if ( ! menuNameControl ) { menuNameControl = new api.controlConstructor.nav_menu_name( menuNameControlId, { label: api.Menus.data.l10n.menuNameLabel, description: api.Menus.data.l10n.newMenuNameDescription, section: section.id, priority: 0 } ); api.control.add( menuNameControl.id, menuNameControl ); menuNameControl.active.set( true ); } menuLocationsControlId = section.id + '[locations]'; menuLocationsControl = api.control( menuLocationsControlId ); if ( ! menuLocationsControl ) { menuLocationsControl = new api.controlConstructor.nav_menu_locations( menuLocationsControlId, { section: section.id, priority: 1, menu_id: '', isCreating: true } ); api.control.add( menuLocationsControlId, menuLocationsControl ); menuLocationsControl.active.set( true ); } newMenuSubmitControlId = section.id + '[submit]'; newMenuSubmitControl = api.control( newMenuSubmitControlId ); if ( !newMenuSubmitControl ) { newMenuSubmitControl = new api.Control( newMenuSubmitControlId, { section: section.id, priority: 1, templateId: 'nav-menu-submit-new-button' } ); api.control.add( newMenuSubmitControlId, newMenuSubmitControl ); newMenuSubmitControl.active.set( true ); } }, /** * Create the new menu with name and location supplied by the user. * * @since 4.9.0 */ submit: function() { var section = this, contentContainer = section.contentContainer, nameInput = contentContainer.find( '.menu-name-field' ).first(), name = nameInput.val(), menuSection; if ( ! name ) { nameInput.addClass( 'invalid' ); nameInput.focus(); return; } menuSection = api.Menus.createNavMenu( name ); // Clear name field. nameInput.val( '' ); nameInput.removeClass( 'invalid' ); contentContainer.find( '.assigned-menu-location input[type=checkbox]' ).each( function() { var checkbox = $( this ), navMenuLocationSetting; if ( checkbox.prop( 'checked' ) ) { navMenuLocationSetting = api( 'nav_menu_locations[' + checkbox.data( 'location-id' ) + ']' ); navMenuLocationSetting.set( menuSection.params.menu_id ); // Reset state for next new menu. checkbox.prop( 'checked', false ); } } ); wp.a11y.speak( api.Menus.data.l10n.menuAdded ); // Focus on the new menu section. menuSection.focus( { completeCallback: function() { menuSection.highlightNewItemButton(); } } ); }, /** * Select a default location. * * This method selects a single location by default so we can support * creating a menu for a specific menu location. * * @since 4.9.0 * * @param {string|null} locationId - The ID of the location to select. `null` clears all selections. * @return {void} */ selectDefaultLocation: function( locationId ) { var locationControl = api.control( this.id + '[locations]' ), locationSelections = {}; if ( locationId !== null ) { locationSelections[ locationId ] = true; } locationControl.setSelections( locationSelections ); } }); /** * wp.customize.Menus.MenuLocationControl * * Customizer control for menu locations (rendered as a ', { type: 'hidden', name: '_method', value: 'GET' } ) ); _.each( previewFrame.query, function( value, key ) { form.append( $( '', { type: 'hidden', name: key, value: value } ) ); } ); previewFrame.container.append( form ); form.trigger( 'submit' ); form.remove(); // No need to keep the form around after submitted. } previewFrame.bind( 'iframe-loading-error', function( error ) { previewFrame.iframe.remove(); // Check if the user is not logged in. if ( 0 === error ) { previewFrame.login( deferred ); return; } // Check for cheaters. if ( -1 === error ) { deferred.rejectWith( previewFrame, [ 'cheatin' ] ); return; } deferred.rejectWith( previewFrame, [ 'request failure' ] ); } ); previewFrame.iframe.one( 'load', function() { loaded = true; if ( ready ) { deferred.resolveWith( previewFrame, [ readyData ] ); } else { setTimeout( function() { deferred.rejectWith( previewFrame, [ 'ready timeout' ] ); }, previewFrame.sensitivity ); } }); }, login: function( deferred ) { var self = this, reject; reject = function() { deferred.rejectWith( self, [ 'logged out' ] ); }; if ( this.triedLogin ) { return reject(); } // Check if we have an admin cookie. $.get( api.settings.url.ajax, { action: 'logged-in' }).fail( reject ).done( function( response ) { var iframe; if ( '1' !== response ) { reject(); } iframe = $( '