File manager - Edit - /home/sarjilsh/cityfoodcenter.co.uk/js.zip
Back
PK 5N]�G��% �% inline-edit-post.min.jsnu �[��� /*! This file is auto-generated */ window.wp=window.wp||{},function(u,h){window.inlineEditPost={init:function(){var i=this,t=u("#inline-edit"),e=u("#bulk-edit"),t=(i.type=u("table.widefat").hasClass("pages")?"page":"post",i.what="#post-",t.on("keyup",function(t){if(27===t.which)return inlineEditPost.revert()}),e.on("keyup",function(t){if(27===t.which)return inlineEditPost.revert()}),u(".cancel",t).on("click",function(){return inlineEditPost.revert()}),u(".save",t).on("click",function(){return inlineEditPost.save(this)}),u("td",t).on("keydown",function(t){if(13===t.which&&!u(t.target).hasClass("cancel"))return inlineEditPost.save(this)}),u(".cancel",e).on("click",function(){return inlineEditPost.revert()}),u('#inline-edit .inline-edit-private input[value="private"]').on("click",function(){var t=u("input.inline-edit-password-input");u(this).prop("checked")?t.val("").prop("disabled",!0):t.prop("disabled",!1)}),u("#the-list").on("click",".editinline",function(){u(this).attr("aria-expanded","true"),inlineEditPost.edit(this)}),u("#inline-edit fieldset.inline-edit-categories").clone());t.find("*[id]").each(function(){this.id="bulk-edit-"+this.id}),u("#bulk-edit").find("fieldset:first").after(t).siblings("fieldset:last").prepend(u("#inline-edit .inline-edit-tags-wrap").clone()),u('select[name="_status"] option[value="future"]',e).remove(),u("#doaction").on("click",function(t){var e;u('#posts-filter .check-column input[type="checkbox"]:checked').length<1||(i.whichBulkButtonId=u(this).attr("id"),e=i.whichBulkButtonId.substr(2),"edit"===u('select[name="'+e+'"]').val()?(t.preventDefault(),i.setBulk()):0<u("form#posts-filter tr.inline-editor").length&&i.revert())})},toggle:function(t){var e=this;"none"===u(e.what+e.getId(t)).css("display")?e.revert():e.edit(t)},setBulk:function(){var n="",t=this.type,a=!0,e=u('tbody th.check-column input[type="checkbox"]:checked'),i={};if(this.revert(),u("#bulk-edit td").attr("colspan",u("th:visible, td:visible",".widefat:first thead").length),u("table.widefat tbody").prepend(u("#bulk-edit")).prepend('<tr class="hidden"></tr>'),u("#bulk-edit").addClass("inline-editor").show(),u('tbody th.check-column input[type="checkbox"]').each(function(){var t,e,i;u(this).prop("checked")&&(a=!1,t=u(this).val(),e=u("#inline_"+t+" .post_title").html()||h.i18n.__("(no title)"),i=h.i18n.sprintf(h.i18n.__("Remove “%s” from Bulk Edit"),e),n+='<li class="ntdelitem"><button type="button" id="_'+t+'" class="button-link ntdelbutton"><span class="screen-reader-text">'+i+'</span></button><span class="ntdeltitle" aria-hidden="true">'+e+"</span></li>")}),a)return this.revert();u("#bulk-titles").html('<ul id="bulk-titles-list" role="list">'+n+"</ul>"),e.each(function(){var t=u(this).val();u("#category_"+t).text().split(",").map(function(t){i[t]||(i[t]=0),i[t]++})}),u('.inline-edit-categories input[name="post_category[]"]').each(function(){var t;i[u(this).val()]==e.length?u(this).prop("checked",!0):0<i[u(this).val()]&&(u(this).prop("indeterminate",!0),u(this).parent().find('input[name="indeterminate_post_category[]"]').length||(t=u(this).parent().text(),u(this).after('<input type="hidden" name="indeterminate_post_category[]" value="'+u(this).val()+'">').attr("aria-label",t.trim()+": "+h.i18n.__("Some selected posts have this category"))))}),u('.inline-edit-categories input[name="post_category[]"]:indeterminate').on("change",function(){u(this).removeAttr("aria-label").parent().find('input[name="indeterminate_post_category[]"]').remove()}),u(".inline-edit-save button").on("click",function(){u('.inline-edit-categories input[name="post_category[]"]').prop("indeterminate",!1)}),u("#bulk-titles .ntdelbutton").click(function(){var t=u(this),e=t.attr("id").substr(1),i=t.parent().prev().children(".ntdelbutton"),t=t.parent().next().children(".ntdelbutton");u("input#cb-select-all-1, input#cb-select-all-2").prop("checked",!1),u('table.widefat input[value="'+e+'"]').prop("checked",!1),u("#_"+e).parent().remove(),h.a11y.speak(h.i18n.__("Item removed."),"assertive"),t.length?t.focus():i.length?i.focus():(u("#bulk-titles-list").remove(),inlineEditPost.revert(),h.a11y.speak(h.i18n.__("All selected items have been removed. Select new items to use Bulk Actions.")))}),"post"===t&&u("tr.inline-editor textarea[data-wp-taxonomy]").each(function(t,e){u(e).autocomplete("instance")||u(e).wpTagsSuggest()}),u("#bulk-edit .inline-edit-wrapper").attr("tabindex","-1").focus(),u("html, body").animate({scrollTop:0},"fast")},edit:function(n){var t,a,e,i,s,r,o,l,d=this,c=!0;for(d.revert(),"object"==typeof n&&(n=d.getId(n)),t=["post_title","post_name","post_author","_status","jj","mm","aa","hh","mn","ss","post_password","post_format","menu_order","page_template"],"page"===d.type&&t.push("post_parent"),a=u("#inline-edit").clone(!0),u("td",a).attr("colspan",u("th:visible, td:visible",".widefat:first thead").length),u("td",a).find("#quick-edit-legend").removeAttr("id"),u("td",a).find('p[id^="quick-edit-"]').removeAttr("id"),u(d.what+n).removeClass("is-expanded").hide().after(a).after('<tr class="hidden"></tr>'),e=u("#inline_"+n),u(':input[name="post_author"] option[value="'+u(".post_author",e).text()+'"]',a).val()||u(':input[name="post_author"]',a).prepend('<option value="'+u(".post_author",e).text()+'">'+u("#post-"+n+" .author").text()+"</option>"),1===u(':input[name="post_author"] option',a).length&&u("label.inline-edit-author",a).hide(),o=0;o<t.length;o++)(l=u("."+t[o],e)).find("img").replaceWith(function(){return this.alt}),l=l.text(),u(':input[name="'+t[o]+'"]',a).val(l);"open"===u(".comment_status",e).text()&&u('input[name="comment_status"]',a).prop("checked",!0),"open"===u(".ping_status",e).text()&&u('input[name="ping_status"]',a).prop("checked",!0),"sticky"===u(".sticky",e).text()&&u('input[name="sticky"]',a).prop("checked",!0),u(".post_category",e).each(function(){var t,e=u(this).text();e&&(t=u(this).attr("id").replace("_"+n,""),u("ul."+t+"-checklist :checkbox",a).val(e.split(",")))}),u(".tags_input",e).each(function(){var t=u(this),e=u(this).attr("id").replace("_"+n,""),e=u("textarea.tax_input_"+e,a),i=h.i18n._x(",","tag delimiter").trim();e.length&&(t.find("img").replaceWith(function(){return this.alt}),(t=t.text())&&(","!==i&&(t=t.replace(/,/g,i)),e.val(t)),e.wpTagsSuggest())});var p,d=u(':input[name="aa"]').val()+"-"+u(':input[name="mm"]').val()+"-"+u(':input[name="jj"]').val(),d=(d+=" "+u(':input[name="hh"]').val()+":"+u(':input[name="mn"]').val()+":"+u(':input[name="ss"]').val(),new Date(d));if(("future"!==(p=u("._status",e).text())&&Date.now()>d?u('select[name="_status"] option[value="future"]',a):u('select[name="_status"] option[value="publish"]',a)).remove(),d=u(".inline-edit-password-input").prop("disabled",!1),"private"===p&&(u('input[name="keep_private"]',a).prop("checked",!0),d.val("").prop("disabled",!0)),0<(i=u('select[name="post_parent"] option[value="'+n+'"]',a)).length){for(s=i[0].className.split("-")[1],r=i;c&&0!==(r=r.next("option")).length;)r[0].className.split("-")[1]<=s?c=!1:(r.remove(),r=i);i.remove()}return u(a).attr("id","edit-"+n).addClass("inline-editor").show(),u(".ptitle",a).trigger("focus"),!1},save:function(n){var t=u(".post_status_page").val()||"";return"object"==typeof n&&(n=this.getId(n)),u("table.widefat .spinner").addClass("is-active"),t={action:"inline-save",post_type:typenow,post_ID:n,edit_date:"true",post_status:t},t=u("#edit-"+n).find(":input").serialize()+"&"+u.param(t),u.post(ajaxurl,t,function(t){var e=u("#edit-"+n+" .inline-edit-save .notice-error"),i=e.find(".error");u("table.widefat .spinner").removeClass("is-active"),t?-1!==t.indexOf("<tr")?(u(inlineEditPost.what+n).siblings("tr.hidden").addBack().remove(),u("#edit-"+n).before(t).remove(),u(inlineEditPost.what+n).hide().fadeIn(400,function(){u(this).find(".editinline").attr("aria-expanded","false").trigger("focus"),h.a11y.speak(h.i18n.__("Changes saved."))})):(t=t.replace(/<.[^<>]*?>/g,""),e.removeClass("hidden"),i.html(t),h.a11y.speak(i.text())):(e.removeClass("hidden"),i.text(h.i18n.__("Error while saving the changes.")),h.a11y.speak(h.i18n.__("Error while saving the changes.")))},"html"),!1},revert:function(){var t=u(".widefat"),e=u(".inline-editor",t).attr("id");return e&&(u(".spinner",t).removeClass("is-active"),("bulk-edit"===e?(u("#bulk-edit",t).removeClass("inline-editor").hide().siblings(".hidden").remove(),u("#bulk-titles").empty(),u("#inlineedit").append(u("#bulk-edit")),u("#"+inlineEditPost.whichBulkButtonId)):(u("#"+e).siblings("tr.hidden").addBack().remove(),e=e.substr(e.lastIndexOf("-")+1),u(this.what+e).show().find(".editinline").attr("aria-expanded","false"))).trigger("focus")),!1},getId:function(t){t=u(t).closest("tr").attr("id").split("-");return t[t.length-1]}},u(function(){inlineEditPost.init()}),u(function(){void 0!==h&&h.heartbeat&&h.heartbeat.interval(10)}).on("heartbeat-tick.wp-check-locked-posts",function(t,e){var n=e["wp-check-locked-posts"]||{},a="wp-locked";u("#the-list tr").each(function(t,e){var i=e.id,e=u(e);n.hasOwnProperty(i)?e.hasClass(a)||(i=n[i],e.find(".column-title .locked-text").text(i.text),e.find(".check-column checkbox").prop("checked",!1),i.avatar_src&&(i=u("<img />",{class:"avatar avatar-18 photo",width:18,height:18,alt:"",src:i.avatar_src,srcset:i.avatar_src_2x?i.avatar_src_2x+" 2x":void 0}),e.find(".column-title .locked-avatar").empty().append(i)),e.addClass(a)):e.hasClass(a)&&e.removeClass(a).find(".locked-info span").empty()})}).on("heartbeat-send.wp-check-locked-posts",function(t,e){var i=[];u("#the-list tr").each(function(t,e){e.id&&i.push(e.id)}),i.length&&(e["wp-check-locked-posts"]=i)})}(jQuery,window.wp);PK 5N]A^[_� � application-passwords.jsnu �[��� /** * @output wp-admin/js/application-passwords.js */ ( function( $ ) { var $appPassSection = $( '#application-passwords-section' ), $newAppPassForm = $appPassSection.find( '.create-application-password' ), $newAppPassField = $newAppPassForm.find( '.input' ), $newAppPassButton = $newAppPassForm.find( '.button' ), $appPassTwrapper = $appPassSection.find( '.application-passwords-list-table-wrapper' ), $appPassTbody = $appPassSection.find( 'tbody' ), $appPassTrNoItems = $appPassTbody.find( '.no-items' ), $removeAllBtn = $( '#revoke-all-application-passwords' ), tmplNewAppPass = wp.template( 'new-application-password' ), tmplAppPassRow = wp.template( 'application-password-row' ), userId = $( '#user_id' ).val(); $newAppPassButton.on( 'click', function( e ) { e.preventDefault(); if ( $newAppPassButton.prop( 'aria-disabled' ) ) { return; } var name = $newAppPassField.val(); if ( 0 === name.length ) { $newAppPassField.trigger( 'focus' ); return; } clearNotices(); $newAppPassButton.prop( 'aria-disabled', true ).addClass( 'disabled' ); var request = { name: name }; /** * Filters the request data used to create a new Application Password. * * @since 5.6.0 * * @param {Object} request The request data. * @param {number} userId The id of the user the password is added for. */ request = wp.hooks.applyFilters( 'wp_application_passwords_new_password_request', request, userId ); wp.apiRequest( { path: '/wp/v2/users/' + userId + '/application-passwords?_locale=user', method: 'POST', data: request } ).always( function() { $newAppPassButton.removeProp( 'aria-disabled' ).removeClass( 'disabled' ); } ).done( function( response ) { $newAppPassField.val( '' ); $newAppPassButton.prop( 'disabled', false ); $newAppPassForm.after( tmplNewAppPass( { name: response.name, password: response.password } ) ); $( '.new-application-password-notice' ).attr( 'tabindex', '-1' ).trigger( 'focus' ); $appPassTbody.prepend( tmplAppPassRow( response ) ); $appPassTwrapper.show(); $appPassTrNoItems.remove(); /** * Fires after an application password has been successfully created. * * @since 5.6.0 * * @param {Object} response The response data from the REST API. * @param {Object} request The request data used to create the password. */ wp.hooks.doAction( 'wp_application_passwords_created_password', response, request ); } ).fail( handleErrorResponse ); } ); $appPassTbody.on( 'click', '.delete', function( e ) { e.preventDefault(); if ( ! window.confirm( wp.i18n.__( 'Are you sure you want to revoke this password? This action cannot be undone.' ) ) ) { return; } var $submitButton = $( this ), $tr = $submitButton.closest( 'tr' ), uuid = $tr.data( 'uuid' ); clearNotices(); $submitButton.prop( 'disabled', true ); wp.apiRequest( { path: '/wp/v2/users/' + userId + '/application-passwords/' + uuid + '?_locale=user', method: 'DELETE' } ).always( function() { $submitButton.prop( 'disabled', false ); } ).done( function( response ) { if ( response.deleted ) { if ( 0 === $tr.siblings().length ) { $appPassTwrapper.hide(); } $tr.remove(); addNotice( wp.i18n.__( 'Application password revoked.' ), 'success' ).trigger( 'focus' ); } } ).fail( handleErrorResponse ); } ); $removeAllBtn.on( 'click', function( e ) { e.preventDefault(); if ( ! window.confirm( wp.i18n.__( 'Are you sure you want to revoke all passwords? This action cannot be undone.' ) ) ) { return; } var $submitButton = $( this ); clearNotices(); $submitButton.prop( 'disabled', true ); wp.apiRequest( { path: '/wp/v2/users/' + userId + '/application-passwords?_locale=user', method: 'DELETE' } ).always( function() { $submitButton.prop( 'disabled', false ); } ).done( function( response ) { if ( response.deleted ) { $appPassTbody.children().remove(); $appPassSection.children( '.new-application-password' ).remove(); $appPassTwrapper.hide(); addNotice( wp.i18n.__( 'All application passwords revoked.' ), 'success' ).trigger( 'focus' ); } } ).fail( handleErrorResponse ); } ); $appPassSection.on( 'click', '.notice-dismiss', function( e ) { e.preventDefault(); var $el = $( this ).parent(); $el.removeAttr( 'role' ); $el.fadeTo( 100, 0, function () { $el.slideUp( 100, function () { $el.remove(); $newAppPassField.trigger( 'focus' ); } ); } ); } ); $newAppPassField.on( 'keypress', function ( e ) { if ( 13 === e.which ) { e.preventDefault(); $newAppPassButton.trigger( 'click' ); } } ); // If there are no items, don't display the table yet. If there are, show it. if ( 0 === $appPassTbody.children( 'tr' ).not( $appPassTrNoItems ).length ) { $appPassTwrapper.hide(); } /** * Handles an error response from the REST API. * * @since 5.6.0 * * @param {jqXHR} xhr The XHR object from the ajax call. * @param {string} textStatus The string categorizing the ajax request's status. * @param {string} errorThrown The HTTP status error text. */ function handleErrorResponse( xhr, textStatus, errorThrown ) { var errorMessage = errorThrown; if ( xhr.responseJSON && xhr.responseJSON.message ) { errorMessage = xhr.responseJSON.message; } addNotice( errorMessage, 'error' ); } /** * Displays a message in the Application Passwords section. * * @since 5.6.0 * * @param {string} message The message to display. * @param {string} type The notice type. Either 'success' or 'error'. * @returns {jQuery} The notice element. */ function addNotice( message, type ) { var $notice = $( '<div></div>' ) .attr( 'role', 'alert' ) .attr( 'tabindex', '-1' ) .addClass( 'is-dismissible notice notice-' + type ) .append( $( '<p></p>' ).text( message ) ) .append( $( '<button></button>' ) .attr( 'type', 'button' ) .addClass( 'notice-dismiss' ) .append( $( '<span></span>' ).addClass( 'screen-reader-text' ).text( wp.i18n.__( 'Dismiss this notice.' ) ) ) ); $newAppPassForm.after( $notice ); return $notice; } /** * Clears notice messages from the Application Passwords section. * * @since 5.6.0 */ function clearNotices() { $( '.notice', $appPassSection ).remove(); } }( jQuery ) ); PK 5N]_Ws>� � color-picker.min.jsnu �[��� /*! This file is auto-generated */ !function(i,t){var a=wp.i18n.__;i.widget("wp.wpColorPicker",{options:{defaultColor:!1,change:!1,clear:!1,hide:!0,palettes:!0,width:255,mode:"hsv",type:"full",slider:"horizontal"},_createHueOnly:function(){var e,o=this,t=o.element;t.hide(),e="hsl("+t.val()+", 100, 50)",t.iris({mode:"hsl",type:"hue",hide:!1,color:e,change:function(e,t){"function"==typeof o.options.change&&o.options.change.call(this,e,t)},width:o.options.width,slider:o.options.slider})},_create:function(){if(i.support.iris){var o=this,e=o.element;if(i.extend(o.options,e.data()),"hue"===o.options.type)return o._createHueOnly();o.close=o.close.bind(o),o.initialValue=e.val(),e.addClass("wp-color-picker"),e.parent("label").length||(e.wrap("<label></label>"),o.wrappingLabelText=i('<span class="screen-reader-text"></span>').insertBefore(e).text(a("Color value"))),o.wrappingLabel=e.parent(),o.wrappingLabel.wrap('<div class="wp-picker-container" />'),o.wrap=o.wrappingLabel.parent(),o.toggler=i('<button type="button" class="button wp-color-result" aria-expanded="false"><span class="wp-color-result-text"></span></button>').insertBefore(o.wrappingLabel).css({backgroundColor:o.initialValue}),o.toggler.find(".wp-color-result-text").text(a("Select Color")),o.pickerContainer=i('<div class="wp-picker-holder" />').insertAfter(o.wrappingLabel),o.button=i('<input type="button" class="button button-small" />'),o.options.defaultColor?o.button.addClass("wp-picker-default").val(a("Default")).attr("aria-label",a("Select default color")):o.button.addClass("wp-picker-clear").val(a("Clear")).attr("aria-label",a("Clear color")),o.wrappingLabel.wrap('<span class="wp-picker-input-wrap hidden" />').after(o.button),o.inputWrapper=e.closest(".wp-picker-input-wrap"),e.iris({target:o.pickerContainer,hide:o.options.hide,width:o.options.width,mode:o.options.mode,palettes:o.options.palettes,change:function(e,t){o.toggler.css({backgroundColor:t.color.toString()}),"function"==typeof o.options.change&&o.options.change.call(this,e,t)}}),e.val(o.initialValue),o._addListeners(),o.options.hide||o.toggler.click()}},_addListeners:function(){var o=this;o.wrap.on("click.wpcolorpicker",function(e){e.stopPropagation()}),o.toggler.on("click",function(){o.toggler.hasClass("wp-picker-open")?o.close():o.open()}),o.element.on("change",function(e){var t=i(this).val();""!==t&&"#"!==t||(o.toggler.css("backgroundColor",""),"function"==typeof o.options.clear&&o.options.clear.call(this,e))}),o.button.on("click",function(e){var t=i(this);t.hasClass("wp-picker-clear")?(o.element.val(""),o.toggler.css("backgroundColor",""),"function"==typeof o.options.clear&&o.options.clear.call(this,e)):t.hasClass("wp-picker-default")&&o.element.val(o.options.defaultColor).change()})},open:function(){this.element.iris("toggle"),this.inputWrapper.removeClass("hidden"),this.wrap.addClass("wp-picker-active"),this.toggler.addClass("wp-picker-open").attr("aria-expanded","true"),i("body").trigger("click.wpcolorpicker").on("click.wpcolorpicker",this.close)},close:function(){this.element.iris("toggle"),this.inputWrapper.addClass("hidden"),this.wrap.removeClass("wp-picker-active"),this.toggler.removeClass("wp-picker-open").attr("aria-expanded","false"),i("body").off("click.wpcolorpicker",this.close)},color:function(e){if(e===t)return this.element.iris("option","color");this.element.iris("option","color",e)},defaultColor:function(e){if(e===t)return this.options.defaultColor;this.options.defaultColor=e}})}(jQuery);PK 5N]"��/ / customize-widgets.jsnu �[��� /** * @output wp-admin/js/customize-widgets.js */ /* global _wpCustomizeWidgetsSettings */ (function( wp, $ ){ if ( ! wp || ! wp.customize ) { return; } // Set up our namespace... var api = wp.customize, l10n; /** * @namespace wp.customize.Widgets */ api.Widgets = api.Widgets || {}; api.Widgets.savedWidgetIds = {}; // Link settings. api.Widgets.data = _wpCustomizeWidgetsSettings || {}; l10n = api.Widgets.data.l10n; /** * wp.customize.Widgets.WidgetModel * * A single widget model. * * @class wp.customize.Widgets.WidgetModel * @augments Backbone.Model */ api.Widgets.WidgetModel = Backbone.Model.extend(/** @lends wp.customize.Widgets.WidgetModel.prototype */{ id: null, temp_id: null, classname: null, control_tpl: null, description: null, is_disabled: null, is_multi: null, multi_number: null, name: null, id_base: null, transport: null, params: [], width: null, height: null, search_matched: true }); /** * wp.customize.Widgets.WidgetCollection * * Collection for widget models. * * @class wp.customize.Widgets.WidgetCollection * @augments Backbone.Collection */ api.Widgets.WidgetCollection = Backbone.Collection.extend(/** @lends wp.customize.Widgets.WidgetCollection.prototype */{ model: api.Widgets.WidgetModel, // Controls searching on the current widget collection // and triggers an update event. doSearch: function( value ) { // Don't do anything if we've already done this search. // Useful because the search handler fires multiple times per keystroke. if ( this.terms === value ) { return; } // Updates terms with the value passed. this.terms = value; // If we have terms, run a search... if ( this.terms.length > 0 ) { this.search( this.terms ); } // If search is blank, set all the widgets as they matched the search to reset the views. if ( this.terms === '' ) { this.each( function ( widget ) { widget.set( 'search_matched', true ); } ); } }, // Performs a search within the collection. // @uses RegExp search: function( term ) { var match, haystack; // Escape the term string for RegExp meta characters. term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' ); // Consider spaces as word delimiters and match the whole string // so matching terms can be combined. term = term.replace( / /g, ')(?=.*' ); match = new RegExp( '^(?=.*' + term + ').+', 'i' ); this.each( function ( data ) { haystack = [ data.get( 'name' ), data.get( 'description' ) ].join( ' ' ); data.set( 'search_matched', match.test( haystack ) ); } ); } }); api.Widgets.availableWidgets = new api.Widgets.WidgetCollection( api.Widgets.data.availableWidgets ); /** * wp.customize.Widgets.SidebarModel * * A single sidebar model. * * @class wp.customize.Widgets.SidebarModel * @augments Backbone.Model */ api.Widgets.SidebarModel = Backbone.Model.extend(/** @lends wp.customize.Widgets.SidebarModel.prototype */{ after_title: null, after_widget: null, before_title: null, before_widget: null, 'class': null, description: null, id: null, name: null, is_rendered: false }); /** * wp.customize.Widgets.SidebarCollection * * Collection for sidebar models. * * @class wp.customize.Widgets.SidebarCollection * @augments Backbone.Collection */ api.Widgets.SidebarCollection = Backbone.Collection.extend(/** @lends wp.customize.Widgets.SidebarCollection.prototype */{ model: api.Widgets.SidebarModel }); api.Widgets.registeredSidebars = new api.Widgets.SidebarCollection( api.Widgets.data.registeredSidebars ); api.Widgets.AvailableWidgetsPanelView = wp.Backbone.View.extend(/** @lends wp.customize.Widgets.AvailableWidgetsPanelView.prototype */{ el: '#available-widgets', events: { 'input #widgets-search': 'search', 'focus .widget-tpl' : 'focus', 'click .widget-tpl' : '_submit', 'keypress .widget-tpl' : '_submit', 'keydown' : 'keyboardAccessible' }, // Cache current selected widget. selected: null, // Cache sidebar control which has opened panel. currentSidebarControl: null, $search: null, $clearResults: null, searchMatchesCount: null, /** * View class for the available widgets panel. * * @constructs wp.customize.Widgets.AvailableWidgetsPanelView * @augments wp.Backbone.View */ initialize: function() { var self = this; this.$search = $( '#widgets-search' ); this.$clearResults = this.$el.find( '.clear-results' ); _.bindAll( this, 'close' ); this.listenTo( this.collection, 'change', this.updateList ); this.updateList(); // Set the initial search count to the number of available widgets. this.searchMatchesCount = this.collection.length; /* * If the available widgets panel is open and the customize controls * are interacted with (i.e. available widgets panel is blurred) then * close the available widgets panel. Also close on back button click. */ $( '#customize-controls, #available-widgets .customize-section-title' ).on( 'click keydown', function( e ) { var isAddNewBtn = $( e.target ).is( '.add-new-widget, .add-new-widget *' ); if ( $( 'body' ).hasClass( 'adding-widget' ) && ! 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' ); } ); // Close the panel if the URL in the preview changes. api.previewer.bind( 'url', this.close ); }, /** * Performs a search and handles selected widget. */ search: _.debounce( function( event ) { var firstVisible; this.collection.doSearch( event.target.value ); // Update the search matches count. this.updateSearchMatchesCount(); // Announce how many search results. this.announceSearchMatches(); // Remove a widget from being selected if it is no longer visible. if ( this.selected && ! this.selected.is( ':visible' ) ) { this.selected.removeClass( 'selected' ); this.selected = null; } // If a widget was selected but the filter value has been cleared out, clear selection. if ( this.selected && ! event.target.value ) { this.selected.removeClass( 'selected' ); this.selected = null; } // If a filter has been entered and a widget hasn't been selected, select the first one shown. if ( ! this.selected && event.target.value ) { firstVisible = this.$el.find( '> .widget-tpl:visible:first' ); if ( firstVisible.length ) { this.select( firstVisible ); } } // Toggle the clear search results button. if ( '' !== event.target.value ) { this.$clearResults.addClass( 'is-visible' ); } else if ( '' === event.target.value ) { this.$clearResults.removeClass( 'is-visible' ); } // Set a CSS class on the search container when there are no search results. if ( ! this.searchMatchesCount ) { this.$el.addClass( 'no-widgets-found' ); } else { this.$el.removeClass( 'no-widgets-found' ); } }, 500 ), /** * Updates the count of the available widgets that have the `search_matched` attribute. */ updateSearchMatchesCount: function() { this.searchMatchesCount = this.collection.where({ search_matched: true }).length; }, /** * Sends a message to the aria-live region to announce how many search results. */ announceSearchMatches: function() { var message = l10n.widgetsFound.replace( '%d', this.searchMatchesCount ) ; if ( ! this.searchMatchesCount ) { message = l10n.noWidgetsFound; } wp.a11y.speak( message ); }, /** * Changes visibility of available widgets. */ updateList: function() { this.collection.each( function( widget ) { var widgetTpl = $( '#widget-tpl-' + widget.id ); widgetTpl.toggle( widget.get( 'search_matched' ) && ! widget.get( 'is_disabled' ) ); if ( widget.get( 'is_disabled' ) && widgetTpl.is( this.selected ) ) { this.selected = null; } } ); }, /** * Highlights a widget. */ select: function( widgetTpl ) { this.selected = $( widgetTpl ); this.selected.siblings( '.widget-tpl' ).removeClass( 'selected' ); this.selected.addClass( 'selected' ); }, /** * Highlights a widget on focus. */ focus: function( event ) { this.select( $( event.currentTarget ) ); }, /** * Handles submit for keypress and click on widget. */ _submit: function( event ) { // Only proceed with keypress if it is Enter or Spacebar. if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) { return; } this.submit( $( event.currentTarget ) ); }, /** * Adds a selected widget to the sidebar. */ submit: function( widgetTpl ) { var widgetId, widget, widgetFormControl; if ( ! widgetTpl ) { widgetTpl = this.selected; } if ( ! widgetTpl || ! this.currentSidebarControl ) { return; } this.select( widgetTpl ); widgetId = $( this.selected ).data( 'widget-id' ); widget = this.collection.findWhere( { id: widgetId } ); if ( ! widget ) { return; } widgetFormControl = this.currentSidebarControl.addWidget( widget.get( 'id_base' ) ); if ( widgetFormControl ) { widgetFormControl.focus(); } this.close(); }, /** * Opens the panel. */ open: function( sidebarControl ) { this.currentSidebarControl = sidebarControl; // Wide widget controls appear over the preview, and so they need to be collapsed when the panel opens. _( this.currentSidebarControl.getWidgetFormControls() ).each( function( control ) { if ( control.params.is_wide ) { control.collapseForm(); } } ); if ( api.section.has( 'publish_settings' ) ) { api.section( 'publish_settings' ).collapse(); } $( 'body' ).addClass( 'adding-widget' ); this.$el.find( '.selected' ).removeClass( 'selected' ); // Reset search. this.collection.doSearch( '' ); if ( ! api.settings.browser.mobile ) { this.$search.trigger( 'focus' ); } }, /** * Closes the panel. */ close: function( options ) { options = options || {}; if ( options.returnFocus && this.currentSidebarControl ) { this.currentSidebarControl.container.find( '.add-new-widget' ).focus(); } this.currentSidebarControl = null; this.selected = null; $( 'body' ).removeClass( 'adding-widget' ); this.$search.val( '' ).trigger( 'input' ); }, /** * Adds keyboard accessibility to the panel. */ keyboardAccessible: function( event ) { var isEnter = ( event.which === 13 ), isEsc = ( event.which === 27 ), isDown = ( event.which === 40 ), isUp = ( event.which === 38 ), isTab = ( event.which === 9 ), isShift = ( event.shiftKey ), selected = null, firstVisible = this.$el.find( '> .widget-tpl:visible:first' ), lastVisible = this.$el.find( '> .widget-tpl:visible:last' ), isSearchFocused = $( event.target ).is( this.$search ), isLastWidgetFocused = $( event.target ).is( '.widget-tpl:visible:last' ); if ( isDown || isUp ) { if ( isDown ) { if ( isSearchFocused ) { selected = firstVisible; } else if ( this.selected && this.selected.nextAll( '.widget-tpl:visible' ).length !== 0 ) { selected = this.selected.nextAll( '.widget-tpl:visible:first' ); } } else if ( isUp ) { if ( isSearchFocused ) { selected = lastVisible; } else if ( this.selected && this.selected.prevAll( '.widget-tpl:visible' ).length !== 0 ) { selected = this.selected.prevAll( '.widget-tpl:visible:first' ); } } this.select( selected ); if ( selected ) { selected.trigger( 'focus' ); } else { this.$search.trigger( 'focus' ); } return; } // If enter pressed but nothing entered, don't do anything. if ( isEnter && ! this.$search.val() ) { return; } if ( isEnter ) { this.submit(); } else if ( isEsc ) { this.close( { returnFocus: true } ); } if ( this.currentSidebarControl && isTab && ( isShift && isSearchFocused || ! isShift && isLastWidgetFocused ) ) { this.currentSidebarControl.container.find( '.add-new-widget' ).focus(); event.preventDefault(); } } }); /** * Handlers for the widget-synced event, organized by widget ID base. * Other widgets may provide their own update handlers by adding * listeners for the widget-synced event. * * @alias wp.customize.Widgets.formSyncHandlers */ api.Widgets.formSyncHandlers = { /** * @param {jQuery.Event} e * @param {jQuery} widget * @param {string} newForm */ rss: function( e, widget, newForm ) { var oldWidgetError = widget.find( '.widget-error:first' ), newWidgetError = $( '<div>' + newForm + '</div>' ).find( '.widget-error:first' ); if ( oldWidgetError.length && newWidgetError.length ) { oldWidgetError.replaceWith( newWidgetError ); } else if ( oldWidgetError.length ) { oldWidgetError.remove(); } else if ( newWidgetError.length ) { widget.find( '.widget-content:first' ).prepend( newWidgetError ); } } }; api.Widgets.WidgetControl = api.Control.extend(/** @lends wp.customize.Widgets.WidgetControl.prototype */{ defaultExpandedArguments: { duration: 'fast', completeCallback: $.noop }, /** * wp.customize.Widgets.WidgetControl * * Customizer control for widgets. * Note that 'widget_form' must match the WP_Widget_Form_Customize_Control::$type * * @since 4.1.0 * * @constructs wp.customize.Widgets.WidgetControl * @augments wp.customize.Control */ initialize: function( id, options ) { var control = this; control.widgetControlEmbedded = false; control.widgetContentEmbedded = false; control.expanded = new api.Value( false ); control.expandedArgumentsQueue = []; control.expanded.bind( function( expanded ) { var args = control.expandedArgumentsQueue.shift(); args = $.extend( {}, control.defaultExpandedArguments, args ); control.onChangeExpanded( expanded, args ); }); control.altNotice = true; api.Control.prototype.initialize.call( control, id, options ); }, /** * Set up the control. * * @since 3.9.0 */ ready: function() { var control = this; /* * Embed a placeholder once the section is expanded. The full widget * form content will be embedded once the control itself is expanded, * and at this point the widget-added event will be triggered. */ if ( ! control.section() ) { control.embedWidgetControl(); } else { api.section( control.section(), function( section ) { var onExpanded = function( isExpanded ) { if ( isExpanded ) { control.embedWidgetControl(); section.expanded.unbind( onExpanded ); } }; if ( section.expanded() ) { onExpanded( true ); } else { section.expanded.bind( onExpanded ); } } ); } }, /** * Embed the .widget element inside the li container. * * @since 4.4.0 */ embedWidgetControl: function() { var control = this, widgetControl; if ( control.widgetControlEmbedded ) { return; } control.widgetControlEmbedded = true; widgetControl = $( control.params.widget_control ); control.container.append( widgetControl ); control._setupModel(); control._setupWideWidget(); control._setupControlToggle(); control._setupWidgetTitle(); control._setupReorderUI(); control._setupHighlightEffects(); control._setupUpdateUI(); control._setupRemoveUI(); }, /** * Embed the actual widget form inside of .widget-content and finally trigger the widget-added event. * * @since 4.4.0 */ embedWidgetContent: function() { var control = this, widgetContent; control.embedWidgetControl(); if ( control.widgetContentEmbedded ) { return; } control.widgetContentEmbedded = true; // Update the notification container element now that the widget content has been embedded. control.notifications.container = control.getNotificationsContainerElement(); control.notifications.render(); widgetContent = $( control.params.widget_content ); control.container.find( '.widget-content:first' ).append( widgetContent ); /* * Trigger widget-added event so that plugins can attach any event * listeners and dynamic UI elements. */ $( document ).trigger( 'widget-added', [ control.container.find( '.widget:first' ) ] ); }, /** * Handle changes to the setting */ _setupModel: function() { var self = this, rememberSavedWidgetId; // Remember saved widgets so we know which to trash (move to inactive widgets sidebar). rememberSavedWidgetId = function() { api.Widgets.savedWidgetIds[self.params.widget_id] = true; }; api.bind( 'ready', rememberSavedWidgetId ); api.bind( 'saved', rememberSavedWidgetId ); this._updateCount = 0; this.isWidgetUpdating = false; this.liveUpdateMode = true; // Update widget whenever model changes. this.setting.bind( function( to, from ) { if ( ! _( from ).isEqual( to ) && ! self.isWidgetUpdating ) { self.updateWidget( { instance: to } ); } } ); }, /** * Add special behaviors for wide widget controls */ _setupWideWidget: function() { var self = this, $widgetInside, $widgetForm, $customizeSidebar, $themeControlsContainer, positionWidget; if ( ! this.params.is_wide || $( window ).width() <= 640 /* max-width breakpoint in customize-controls.css */ ) { return; } $widgetInside = this.container.find( '.widget-inside' ); $widgetForm = $widgetInside.find( '> .form' ); $customizeSidebar = $( '.wp-full-overlay-sidebar-content:first' ); this.container.addClass( 'wide-widget-control' ); this.container.find( '.form:first' ).css( { 'max-width': this.params.width, 'min-height': this.params.height } ); /** * Keep the widget-inside positioned so the top of fixed-positioned * element is at the same top position as the widget-top. When the * widget-top is scrolled out of view, keep the widget-top in view; * likewise, don't allow the widget to drop off the bottom of the window. * If a widget is too tall to fit in the window, don't let the height * exceed the window height so that the contents of the widget control * will become scrollable (overflow:auto). */ positionWidget = function() { var offsetTop = self.container.offset().top, windowHeight = $( window ).height(), formHeight = $widgetForm.outerHeight(), top; $widgetInside.css( 'max-height', windowHeight ); top = Math.max( 0, // Prevent top from going off screen. Math.min( Math.max( offsetTop, 0 ), // Distance widget in panel is from top of screen. windowHeight - formHeight // Flush up against bottom of screen. ) ); $widgetInside.css( 'top', top ); }; $themeControlsContainer = $( '#customize-theme-controls' ); this.container.on( 'expand', function() { positionWidget(); $customizeSidebar.on( 'scroll', positionWidget ); $( window ).on( 'resize', positionWidget ); $themeControlsContainer.on( 'expanded collapsed', positionWidget ); } ); this.container.on( 'collapsed', function() { $customizeSidebar.off( 'scroll', positionWidget ); $( window ).off( 'resize', positionWidget ); $themeControlsContainer.off( 'expanded collapsed', positionWidget ); } ); // Reposition whenever a sidebar's widgets are changed. api.each( function( setting ) { if ( 0 === setting.id.indexOf( 'sidebars_widgets[' ) ) { setting.bind( function() { if ( self.container.hasClass( 'expanded' ) ) { positionWidget(); } } ); } } ); }, /** * Show/hide the control when clicking on the form title, when clicking * the close button */ _setupControlToggle: function() { var self = this, $closeBtn; this.container.find( '.widget-top' ).on( 'click', function( e ) { e.preventDefault(); var sidebarWidgetsControl = self.getSidebarWidgetsControl(); if ( sidebarWidgetsControl.isReordering ) { return; } self.expanded( ! self.expanded() ); } ); $closeBtn = this.container.find( '.widget-control-close' ); $closeBtn.on( 'click', function() { self.collapse(); self.container.find( '.widget-top .widget-action:first' ).focus(); // Keyboard accessibility. } ); }, /** * Update the title of the form if a title field is entered */ _setupWidgetTitle: function() { var self = this, updateTitle; updateTitle = function() { var title = self.setting().title, inWidgetTitle = self.container.find( '.in-widget-title' ); if ( title ) { inWidgetTitle.text( ': ' + title ); } else { inWidgetTitle.text( '' ); } }; this.setting.bind( updateTitle ); updateTitle(); }, /** * Set up the widget-reorder-nav */ _setupReorderUI: function() { var self = this, selectSidebarItem, $moveWidgetArea, $reorderNav, updateAvailableSidebars, template; /** * select the provided sidebar list item in the move widget area * * @param {jQuery} li */ selectSidebarItem = function( li ) { li.siblings( '.selected' ).removeClass( 'selected' ); li.addClass( 'selected' ); var isSelfSidebar = ( li.data( 'id' ) === self.params.sidebar_id ); self.container.find( '.move-widget-btn' ).prop( 'disabled', isSelfSidebar ); }; /** * Add the widget reordering elements to the widget control */ this.container.find( '.widget-title-action' ).after( $( api.Widgets.data.tpl.widgetReorderNav ) ); template = _.template( api.Widgets.data.tpl.moveWidgetArea ); $moveWidgetArea = $( template( { sidebars: _( api.Widgets.registeredSidebars.toArray() ).pluck( 'attributes' ) } ) ); this.container.find( '.widget-top' ).after( $moveWidgetArea ); /** * Update available sidebars when their rendered state changes */ updateAvailableSidebars = function() { var $sidebarItems = $moveWidgetArea.find( 'li' ), selfSidebarItem, renderedSidebarCount = 0; selfSidebarItem = $sidebarItems.filter( function(){ return $( this ).data( 'id' ) === self.params.sidebar_id; } ); $sidebarItems.each( function() { var li = $( this ), sidebarId, sidebar, sidebarIsRendered; sidebarId = li.data( 'id' ); sidebar = api.Widgets.registeredSidebars.get( sidebarId ); sidebarIsRendered = sidebar.get( 'is_rendered' ); li.toggle( sidebarIsRendered ); if ( sidebarIsRendered ) { renderedSidebarCount += 1; } if ( li.hasClass( 'selected' ) && ! sidebarIsRendered ) { selectSidebarItem( selfSidebarItem ); } } ); if ( renderedSidebarCount > 1 ) { self.container.find( '.move-widget' ).show(); } else { self.container.find( '.move-widget' ).hide(); } }; updateAvailableSidebars(); api.Widgets.registeredSidebars.on( 'change:is_rendered', updateAvailableSidebars ); /** * Handle clicks for up/down/move on the reorder nav */ $reorderNav = this.container.find( '.widget-reorder-nav' ); $reorderNav.find( '.move-widget, .move-widget-down, .move-widget-up' ).each( function() { $( this ).prepend( self.container.find( '.widget-title' ).text() + ': ' ); } ).on( 'click keypress', function( event ) { if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) { return; } $( this ).trigger( 'focus' ); if ( $( this ).is( '.move-widget' ) ) { self.toggleWidgetMoveArea(); } else { var isMoveDown = $( this ).is( '.move-widget-down' ), isMoveUp = $( this ).is( '.move-widget-up' ), i = self.getWidgetSidebarPosition(); if ( ( isMoveUp && i === 0 ) || ( isMoveDown && i === self.getSidebarWidgetsControl().setting().length - 1 ) ) { return; } if ( isMoveUp ) { self.moveUp(); wp.a11y.speak( l10n.widgetMovedUp ); } else { self.moveDown(); wp.a11y.speak( l10n.widgetMovedDown ); } $( this ).trigger( 'focus' ); // Re-focus after the container was moved. } } ); /** * Handle selecting a sidebar to move to */ this.container.find( '.widget-area-select' ).on( 'click keypress', 'li', function( event ) { if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) { return; } event.preventDefault(); selectSidebarItem( $( this ) ); } ); /** * Move widget to another sidebar */ this.container.find( '.move-widget-btn' ).click( function() { self.getSidebarWidgetsControl().toggleReordering( false ); var oldSidebarId = self.params.sidebar_id, newSidebarId = self.container.find( '.widget-area-select li.selected' ).data( 'id' ), oldSidebarWidgetsSetting, newSidebarWidgetsSetting, oldSidebarWidgetIds, newSidebarWidgetIds, i; oldSidebarWidgetsSetting = api( 'sidebars_widgets[' + oldSidebarId + ']' ); newSidebarWidgetsSetting = api( 'sidebars_widgets[' + newSidebarId + ']' ); oldSidebarWidgetIds = Array.prototype.slice.call( oldSidebarWidgetsSetting() ); newSidebarWidgetIds = Array.prototype.slice.call( newSidebarWidgetsSetting() ); i = self.getWidgetSidebarPosition(); oldSidebarWidgetIds.splice( i, 1 ); newSidebarWidgetIds.push( self.params.widget_id ); oldSidebarWidgetsSetting( oldSidebarWidgetIds ); newSidebarWidgetsSetting( newSidebarWidgetIds ); self.focus(); } ); }, /** * Highlight widgets in preview when interacted with in the Customizer */ _setupHighlightEffects: function() { var self = this; // Highlight whenever hovering or clicking over the form. this.container.on( 'mouseenter click', function() { self.setting.previewer.send( 'highlight-widget', self.params.widget_id ); } ); // Highlight when the setting is updated. this.setting.bind( function() { self.setting.previewer.send( 'highlight-widget', self.params.widget_id ); } ); }, /** * Set up event handlers for widget updating */ _setupUpdateUI: function() { var self = this, $widgetRoot, $widgetContent, $saveBtn, updateWidgetDebounced, formSyncHandler; $widgetRoot = this.container.find( '.widget:first' ); $widgetContent = $widgetRoot.find( '.widget-content:first' ); // Configure update button. $saveBtn = this.container.find( '.widget-control-save' ); $saveBtn.val( l10n.saveBtnLabel ); $saveBtn.attr( 'title', l10n.saveBtnTooltip ); $saveBtn.removeClass( 'button-primary' ); $saveBtn.on( 'click', function( e ) { e.preventDefault(); self.updateWidget( { disable_form: true } ); // @todo disable_form is unused? } ); updateWidgetDebounced = _.debounce( function() { self.updateWidget(); }, 250 ); // Trigger widget form update when hitting Enter within an input. $widgetContent.on( 'keydown', 'input', function( e ) { if ( 13 === e.which ) { // Enter. e.preventDefault(); self.updateWidget( { ignoreActiveElement: true } ); } } ); // Handle widgets that support live previews. $widgetContent.on( 'change input propertychange', ':input', function( e ) { if ( ! self.liveUpdateMode ) { return; } if ( e.type === 'change' || ( this.checkValidity && this.checkValidity() ) ) { updateWidgetDebounced(); } } ); // Remove loading indicators when the setting is saved and the preview updates. this.setting.previewer.channel.bind( 'synced', function() { self.container.removeClass( 'previewer-loading' ); } ); api.previewer.bind( 'widget-updated', function( updatedWidgetId ) { if ( updatedWidgetId === self.params.widget_id ) { self.container.removeClass( 'previewer-loading' ); } } ); formSyncHandler = api.Widgets.formSyncHandlers[ this.params.widget_id_base ]; if ( formSyncHandler ) { $( document ).on( 'widget-synced', function( e, widget ) { if ( $widgetRoot.is( widget ) ) { formSyncHandler.apply( document, arguments ); } } ); } }, /** * Update widget control to indicate whether it is currently rendered. * * Overrides api.Control.toggle() * * @since 4.1.0 * * @param {boolean} active * @param {Object} args * @param {function} args.completeCallback */ onChangeActive: function ( active, args ) { // Note: there is a second 'args' parameter being passed, merged on top of this.defaultActiveArguments. this.container.toggleClass( 'widget-rendered', active ); if ( args.completeCallback ) { args.completeCallback(); } }, /** * Set up event handlers for widget removal */ _setupRemoveUI: function() { var self = this, $removeBtn, replaceDeleteWithRemove; // Configure remove button. $removeBtn = this.container.find( '.widget-control-remove' ); $removeBtn.on( 'click', function() { // Find an adjacent element to add focus to when this widget goes away. var $adjacentFocusTarget; if ( self.container.next().is( '.customize-control-widget_form' ) ) { $adjacentFocusTarget = self.container.next().find( '.widget-action:first' ); } else if ( self.container.prev().is( '.customize-control-widget_form' ) ) { $adjacentFocusTarget = self.container.prev().find( '.widget-action:first' ); } else { $adjacentFocusTarget = self.container.next( '.customize-control-sidebar_widgets' ).find( '.add-new-widget:first' ); } self.container.slideUp( function() { var sidebarsWidgetsControl = api.Widgets.getSidebarWidgetControlContainingWidget( self.params.widget_id ), sidebarWidgetIds, i; if ( ! sidebarsWidgetsControl ) { return; } sidebarWidgetIds = sidebarsWidgetsControl.setting().slice(); i = _.indexOf( sidebarWidgetIds, self.params.widget_id ); if ( -1 === i ) { return; } sidebarWidgetIds.splice( i, 1 ); sidebarsWidgetsControl.setting( sidebarWidgetIds ); $adjacentFocusTarget.focus(); // Keyboard accessibility. } ); } ); replaceDeleteWithRemove = function() { $removeBtn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the button as "Delete". $removeBtn.attr( 'title', l10n.removeBtnTooltip ); }; if ( this.params.is_new ) { api.bind( 'saved', replaceDeleteWithRemove ); } else { replaceDeleteWithRemove(); } }, /** * Find all inputs in a widget container that should be considered when * comparing the loaded form with the sanitized form, whose fields will * be aligned to copy the sanitized over. The elements returned by this * are passed into this._getInputsSignature(), and they are iterated * over when copying sanitized values over to the form loaded. * * @param {jQuery} container element in which to look for inputs * @return {jQuery} inputs * @private */ _getInputs: function( container ) { return $( container ).find( ':input[name]' ); }, /** * Iterate over supplied inputs and create a signature string for all of them together. * This string can be used to compare whether or not the form has all of the same fields. * * @param {jQuery} inputs * @return {string} * @private */ _getInputsSignature: function( inputs ) { var inputsSignatures = _( inputs ).map( function( input ) { var $input = $( input ), signatureParts; if ( $input.is( ':checkbox, :radio' ) ) { signatureParts = [ $input.attr( 'id' ), $input.attr( 'name' ), $input.prop( 'value' ) ]; } else { signatureParts = [ $input.attr( 'id' ), $input.attr( 'name' ) ]; } return signatureParts.join( ',' ); } ); return inputsSignatures.join( ';' ); }, /** * Get the state for an input depending on its type. * * @param {jQuery|Element} input * @return {string|boolean|Array|*} * @private */ _getInputState: function( input ) { input = $( input ); if ( input.is( ':radio, :checkbox' ) ) { return input.prop( 'checked' ); } else if ( input.is( 'select[multiple]' ) ) { return input.find( 'option:selected' ).map( function () { return $( this ).val(); } ).get(); } else { return input.val(); } }, /** * Update an input's state based on its type. * * @param {jQuery|Element} input * @param {string|boolean|Array|*} state * @private */ _setInputState: function ( input, state ) { input = $( input ); if ( input.is( ':radio, :checkbox' ) ) { input.prop( 'checked', state ); } else if ( input.is( 'select[multiple]' ) ) { if ( ! Array.isArray( state ) ) { state = []; } else { // Make sure all state items are strings since the DOM value is a string. state = _.map( state, function ( value ) { return String( value ); } ); } input.find( 'option' ).each( function () { $( this ).prop( 'selected', -1 !== _.indexOf( state, String( this.value ) ) ); } ); } else { input.val( state ); } }, /*********************************************************************** * Begin public API methods **********************************************************************/ /** * @return {wp.customize.controlConstructor.sidebar_widgets[]} */ getSidebarWidgetsControl: function() { var settingId, sidebarWidgetsControl; settingId = 'sidebars_widgets[' + this.params.sidebar_id + ']'; sidebarWidgetsControl = api.control( settingId ); if ( ! sidebarWidgetsControl ) { return; } return sidebarWidgetsControl; }, /** * Submit the widget form via Ajax and get back the updated instance, * along with the new widget control form to render. * * @param {Object} [args] * @param {Object|null} [args.instance=null] When the model changes, the instance is sent here; otherwise, the inputs from the form are used * @param {Function|null} [args.complete=null] Function which is called when the request finishes. Context is bound to the control. First argument is any error. Following arguments are for success. * @param {boolean} [args.ignoreActiveElement=false] Whether or not updating a field will be deferred if focus is still on the element. */ updateWidget: function( args ) { var self = this, instanceOverride, completeCallback, $widgetRoot, $widgetContent, updateNumber, params, data, $inputs, processing, jqxhr, isChanged; // The updateWidget logic requires that the form fields to be fully present. self.embedWidgetContent(); args = $.extend( { instance: null, complete: null, ignoreActiveElement: false }, args ); instanceOverride = args.instance; completeCallback = args.complete; this._updateCount += 1; updateNumber = this._updateCount; $widgetRoot = this.container.find( '.widget:first' ); $widgetContent = $widgetRoot.find( '.widget-content:first' ); // Remove a previous error message. $widgetContent.find( '.widget-error' ).remove(); this.container.addClass( 'widget-form-loading' ); this.container.addClass( 'previewer-loading' ); processing = api.state( 'processing' ); processing( processing() + 1 ); if ( ! this.liveUpdateMode ) { this.container.addClass( 'widget-form-disabled' ); } params = {}; params.action = 'update-widget'; params.wp_customize = 'on'; params.nonce = api.settings.nonce['update-widget']; params.customize_theme = api.settings.theme.stylesheet; params.customized = wp.customize.previewer.query().customized; data = $.param( params ); $inputs = this._getInputs( $widgetContent ); /* * Store the value we're submitting in data so that when the response comes back, * we know if it got sanitized; if there is no difference in the sanitized value, * then we do not need to touch the UI and mess up the user's ongoing editing. */ $inputs.each( function() { $( this ).data( 'state' + updateNumber, self._getInputState( this ) ); } ); if ( instanceOverride ) { data += '&' + $.param( { 'sanitized_widget_setting': JSON.stringify( instanceOverride ) } ); } else { data += '&' + $inputs.serialize(); } data += '&' + $widgetContent.find( '~ :input' ).serialize(); if ( this._previousUpdateRequest ) { this._previousUpdateRequest.abort(); } jqxhr = $.post( wp.ajax.settings.url, data ); this._previousUpdateRequest = jqxhr; jqxhr.done( function( r ) { var message, sanitizedForm, $sanitizedInputs, hasSameInputsInResponse, isLiveUpdateAborted = false; // Check if the user is logged out. if ( '0' === r ) { api.previewer.preview.iframe.hide(); api.previewer.login().done( function() { self.updateWidget( args ); api.previewer.preview.iframe.show(); } ); return; } // Check for cheaters. if ( '-1' === r ) { api.previewer.cheatin(); return; } if ( r.success ) { sanitizedForm = $( '<div>' + r.data.form + '</div>' ); $sanitizedInputs = self._getInputs( sanitizedForm ); hasSameInputsInResponse = self._getInputsSignature( $inputs ) === self._getInputsSignature( $sanitizedInputs ); // Restore live update mode if sanitized fields are now aligned with the existing fields. if ( hasSameInputsInResponse && ! self.liveUpdateMode ) { self.liveUpdateMode = true; self.container.removeClass( 'widget-form-disabled' ); self.container.find( 'input[name="savewidget"]' ).hide(); } // Sync sanitized field states to existing fields if they are aligned. if ( hasSameInputsInResponse && self.liveUpdateMode ) { $inputs.each( function( i ) { var $input = $( this ), $sanitizedInput = $( $sanitizedInputs[i] ), submittedState, sanitizedState, canUpdateState; submittedState = $input.data( 'state' + updateNumber ); sanitizedState = self._getInputState( $sanitizedInput ); $input.data( 'sanitized', sanitizedState ); canUpdateState = ( ! _.isEqual( submittedState, sanitizedState ) && ( args.ignoreActiveElement || ! $input.is( document.activeElement ) ) ); if ( canUpdateState ) { self._setInputState( $input, sanitizedState ); } } ); $( document ).trigger( 'widget-synced', [ $widgetRoot, r.data.form ] ); // Otherwise, if sanitized fields are not aligned with existing fields, disable live update mode if enabled. } else if ( self.liveUpdateMode ) { self.liveUpdateMode = false; self.container.find( 'input[name="savewidget"]' ).show(); isLiveUpdateAborted = true; // Otherwise, replace existing form with the sanitized form. } else { $widgetContent.html( r.data.form ); self.container.removeClass( 'widget-form-disabled' ); $( document ).trigger( 'widget-updated', [ $widgetRoot ] ); } /** * If the old instance is identical to the new one, there is nothing new * needing to be rendered, and so we can preempt the event for the * preview finishing loading. */ isChanged = ! isLiveUpdateAborted && ! _( self.setting() ).isEqual( r.data.instance ); if ( isChanged ) { self.isWidgetUpdating = true; // Suppress triggering another updateWidget. self.setting( r.data.instance ); self.isWidgetUpdating = false; } else { // No change was made, so stop the spinner now instead of when the preview would updates. self.container.removeClass( 'previewer-loading' ); } if ( completeCallback ) { completeCallback.call( self, null, { noChange: ! isChanged, ajaxFinished: true } ); } } else { // General error message. message = l10n.error; if ( r.data && r.data.message ) { message = r.data.message; } if ( completeCallback ) { completeCallback.call( self, message ); } else { $widgetContent.prepend( '<p class="widget-error"><strong>' + message + '</strong></p>' ); } } } ); jqxhr.fail( function( jqXHR, textStatus ) { if ( completeCallback ) { completeCallback.call( self, textStatus ); } } ); jqxhr.always( function() { self.container.removeClass( 'widget-form-loading' ); $inputs.each( function() { $( this ).removeData( 'state' + updateNumber ); } ); processing( processing() - 1 ); } ); }, /** * Expand the accordion section containing a control */ expandControlSection: function() { api.Control.prototype.expand.call( this ); }, /** * @since 4.1.0 * * @param {Boolean} expanded * @param {Object} [params] * @return {Boolean} False if state already applied. */ _toggleExpanded: api.Section.prototype._toggleExpanded, /** * @since 4.1.0 * * @param {Object} [params] * @return {Boolean} False if already expanded. */ expand: api.Section.prototype.expand, /** * Expand the widget form control * * @deprecated 4.1.0 Use this.expand() instead. */ expandForm: function() { this.expand(); }, /** * @since 4.1.0 * * @param {Object} [params] * @return {Boolean} False if already collapsed. */ collapse: api.Section.prototype.collapse, /** * Collapse the widget form control * * @deprecated 4.1.0 Use this.collapse() instead. */ collapseForm: function() { this.collapse(); }, /** * Expand or collapse the widget control * * @deprecated this is poor naming, and it is better to directly set control.expanded( showOrHide ) * * @param {boolean|undefined} [showOrHide] If not supplied, will be inverse of current visibility */ toggleForm: function( showOrHide ) { if ( typeof showOrHide === 'undefined' ) { showOrHide = ! this.expanded(); } this.expanded( showOrHide ); }, /** * Respond to change in the expanded state. * * @param {boolean} expanded * @param {Object} args merged on top of this.defaultActiveArguments */ onChangeExpanded: function ( expanded, args ) { var self = this, $widget, $inside, complete, prevComplete, expandControl, $toggleBtn; self.embedWidgetControl(); // Make sure the outer form is embedded so that the expanded state can be set in the UI. if ( expanded ) { self.embedWidgetContent(); } // If the expanded state is unchanged only manipulate container expanded states. if ( args.unchanged ) { if ( expanded ) { api.Control.prototype.expand.call( self, { completeCallback: args.completeCallback }); } return; } $widget = this.container.find( 'div.widget:first' ); $inside = $widget.find( '.widget-inside:first' ); $toggleBtn = this.container.find( '.widget-top button.widget-action' ); expandControl = function() { // Close all other widget controls before expanding this one. api.control.each( function( otherControl ) { if ( self.params.type === otherControl.params.type && self !== otherControl ) { otherControl.collapse(); } } ); complete = function() { self.container.removeClass( 'expanding' ); self.container.addClass( 'expanded' ); $widget.addClass( 'open' ); $toggleBtn.attr( 'aria-expanded', 'true' ); self.container.trigger( 'expanded' ); }; if ( args.completeCallback ) { prevComplete = complete; complete = function () { prevComplete(); args.completeCallback(); }; } if ( self.params.is_wide ) { $inside.fadeIn( args.duration, complete ); } else { $inside.slideDown( args.duration, complete ); } self.container.trigger( 'expand' ); self.container.addClass( 'expanding' ); }; if ( $toggleBtn.attr( 'aria-expanded' ) === 'false' ) { if ( api.section.has( self.section() ) ) { api.section( self.section() ).expand( { completeCallback: expandControl } ); } else { expandControl(); } } else { complete = function() { self.container.removeClass( 'collapsing' ); self.container.removeClass( 'expanded' ); $widget.removeClass( 'open' ); $toggleBtn.attr( 'aria-expanded', 'false' ); self.container.trigger( 'collapsed' ); }; if ( args.completeCallback ) { prevComplete = complete; complete = function () { prevComplete(); args.completeCallback(); }; } self.container.trigger( 'collapse' ); self.container.addClass( 'collapsing' ); if ( self.params.is_wide ) { $inside.fadeOut( args.duration, complete ); } else { $inside.slideUp( args.duration, function() { $widget.css( { width:'', margin:'' } ); complete(); } ); } } }, /** * Get the position (index) of the widget in the containing sidebar * * @return {number} */ getWidgetSidebarPosition: function() { var sidebarWidgetIds, position; sidebarWidgetIds = this.getSidebarWidgetsControl().setting(); position = _.indexOf( sidebarWidgetIds, this.params.widget_id ); if ( position === -1 ) { return; } return position; }, /** * Move widget up one in the sidebar */ moveUp: function() { this._moveWidgetByOne( -1 ); }, /** * Move widget up one in the sidebar */ moveDown: function() { this._moveWidgetByOne( 1 ); }, /** * @private * * @param {number} offset 1|-1 */ _moveWidgetByOne: function( offset ) { var i, sidebarWidgetsSetting, sidebarWidgetIds, adjacentWidgetId; i = this.getWidgetSidebarPosition(); sidebarWidgetsSetting = this.getSidebarWidgetsControl().setting; sidebarWidgetIds = Array.prototype.slice.call( sidebarWidgetsSetting() ); // Clone. adjacentWidgetId = sidebarWidgetIds[i + offset]; sidebarWidgetIds[i + offset] = this.params.widget_id; sidebarWidgetIds[i] = adjacentWidgetId; sidebarWidgetsSetting( sidebarWidgetIds ); }, /** * Toggle visibility of the widget move area * * @param {boolean} [showOrHide] */ toggleWidgetMoveArea: function( showOrHide ) { var self = this, $moveWidgetArea; $moveWidgetArea = this.container.find( '.move-widget-area' ); if ( typeof showOrHide === 'undefined' ) { showOrHide = ! $moveWidgetArea.hasClass( 'active' ); } if ( showOrHide ) { // Reset the selected sidebar. $moveWidgetArea.find( '.selected' ).removeClass( 'selected' ); $moveWidgetArea.find( 'li' ).filter( function() { return $( this ).data( 'id' ) === self.params.sidebar_id; } ).addClass( 'selected' ); this.container.find( '.move-widget-btn' ).prop( 'disabled', true ); } $moveWidgetArea.toggleClass( 'active', showOrHide ); }, /** * Highlight the widget control and section */ highlightSectionAndControl: function() { var $target; if ( this.container.is( ':hidden' ) ) { $target = this.container.closest( '.control-section' ); } else { $target = this.container; } $( '.highlighted' ).removeClass( 'highlighted' ); $target.addClass( 'highlighted' ); setTimeout( function() { $target.removeClass( 'highlighted' ); }, 500 ); } } ); /** * wp.customize.Widgets.WidgetsPanel * * Customizer panel containing the widget area sections. * * @since 4.4.0 * * @class wp.customize.Widgets.WidgetsPanel * @augments wp.customize.Panel */ api.Widgets.WidgetsPanel = api.Panel.extend(/** @lends wp.customize.Widgets.WigetsPanel.prototype */{ /** * Add and manage the display of the no-rendered-areas notice. * * @since 4.4.0 */ ready: function () { var panel = this; api.Panel.prototype.ready.call( panel ); panel.deferred.embedded.done(function() { var panelMetaContainer, noticeContainer, updateNotice, getActiveSectionCount, shouldShowNotice; panelMetaContainer = panel.container.find( '.panel-meta' ); // @todo This should use the Notifications API introduced to panels. See <https://core.trac.wordpress.org/ticket/38794>. noticeContainer = $( '<div></div>', { 'class': 'no-widget-areas-rendered-notice', 'role': 'alert' }); panelMetaContainer.append( noticeContainer ); /** * Get the number of active sections in the panel. * * @return {number} Number of active sidebar sections. */ getActiveSectionCount = function() { return _.filter( panel.sections(), function( section ) { return 'sidebar' === section.params.type && section.active(); } ).length; }; /** * Determine whether or not the notice should be displayed. * * @return {boolean} */ shouldShowNotice = function() { var activeSectionCount = getActiveSectionCount(); if ( 0 === activeSectionCount ) { return true; } else { return activeSectionCount !== api.Widgets.data.registeredSidebars.length; } }; /** * Update the notice. * * @return {void} */ updateNotice = function() { var activeSectionCount = getActiveSectionCount(), someRenderedMessage, nonRenderedAreaCount, registeredAreaCount; noticeContainer.empty(); registeredAreaCount = api.Widgets.data.registeredSidebars.length; if ( activeSectionCount !== registeredAreaCount ) { if ( 0 !== activeSectionCount ) { nonRenderedAreaCount = registeredAreaCount - activeSectionCount; someRenderedMessage = l10n.someAreasShown[ nonRenderedAreaCount ]; } else { someRenderedMessage = l10n.noAreasShown; } if ( someRenderedMessage ) { noticeContainer.append( $( '<p></p>', { text: someRenderedMessage } ) ); } noticeContainer.append( $( '<p></p>', { text: l10n.navigatePreview } ) ); } }; updateNotice(); /* * Set the initial visibility state for rendered notice. * Update the visibility of the notice whenever a reflow happens. */ noticeContainer.toggle( shouldShowNotice() ); api.previewer.deferred.active.done( function () { noticeContainer.toggle( shouldShowNotice() ); }); api.bind( 'pane-contents-reflowed', function() { var duration = ( 'resolved' === api.previewer.deferred.active.state() ) ? 'fast' : 0; updateNotice(); if ( shouldShowNotice() ) { noticeContainer.slideDown( duration ); } else { noticeContainer.slideUp( duration ); } }); }); }, /** * Allow an active widgets panel to be contextually active even when it has no active sections (widget areas). * * This ensures that the widgets panel appears even when there are no * sidebars displayed on the URL currently being previewed. * * @since 4.4.0 * * @return {boolean} */ isContextuallyActive: function() { var panel = this; return panel.active(); } }); /** * wp.customize.Widgets.SidebarSection * * Customizer section representing a widget area widget * * @since 4.1.0 * * @class wp.customize.Widgets.SidebarSection * @augments wp.customize.Section */ api.Widgets.SidebarSection = api.Section.extend(/** @lends wp.customize.Widgets.SidebarSection.prototype */{ /** * Sync the section's active state back to the Backbone model's is_rendered attribute * * @since 4.1.0 */ ready: function () { var section = this, registeredSidebar; api.Section.prototype.ready.call( this ); registeredSidebar = api.Widgets.registeredSidebars.get( section.params.sidebarId ); section.active.bind( function ( active ) { registeredSidebar.set( 'is_rendered', active ); }); registeredSidebar.set( 'is_rendered', section.active() ); } }); /** * wp.customize.Widgets.SidebarControl * * Customizer control for widgets. * Note that 'sidebar_widgets' must match the WP_Widget_Area_Customize_Control::$type * * @since 3.9.0 * * @class wp.customize.Widgets.SidebarControl * @augments wp.customize.Control */ api.Widgets.SidebarControl = api.Control.extend(/** @lends wp.customize.Widgets.SidebarControl.prototype */{ /** * Set up the control */ ready: function() { this.$controlSection = this.container.closest( '.control-section' ); this.$sectionContent = this.container.closest( '.accordion-section-content' ); this._setupModel(); this._setupSortable(); this._setupAddition(); this._applyCardinalOrderClassNames(); }, /** * Update ordering of widget control forms when the setting is updated */ _setupModel: function() { var self = this; this.setting.bind( function( newWidgetIds, oldWidgetIds ) { var widgetFormControls, removedWidgetIds, priority; removedWidgetIds = _( oldWidgetIds ).difference( newWidgetIds ); // Filter out any persistent widget IDs for widgets which have been deactivated. newWidgetIds = _( newWidgetIds ).filter( function( newWidgetId ) { var parsedWidgetId = parseWidgetId( newWidgetId ); return !! api.Widgets.availableWidgets.findWhere( { id_base: parsedWidgetId.id_base } ); } ); widgetFormControls = _( newWidgetIds ).map( function( widgetId ) { var widgetFormControl = api.Widgets.getWidgetFormControlForWidget( widgetId ); if ( ! widgetFormControl ) { widgetFormControl = self.addWidget( widgetId ); } return widgetFormControl; } ); // Sort widget controls to their new positions. widgetFormControls.sort( function( a, b ) { var aIndex = _.indexOf( newWidgetIds, a.params.widget_id ), bIndex = _.indexOf( newWidgetIds, b.params.widget_id ); return aIndex - bIndex; }); priority = 0; _( widgetFormControls ).each( function ( control ) { control.priority( priority ); control.section( self.section() ); priority += 1; }); self.priority( priority ); // Make sure sidebar control remains at end. // Re-sort widget form controls (including widgets form other sidebars newly moved here). self._applyCardinalOrderClassNames(); // If the widget was dragged into the sidebar, make sure the sidebar_id param is updated. _( widgetFormControls ).each( function( widgetFormControl ) { widgetFormControl.params.sidebar_id = self.params.sidebar_id; } ); // Cleanup after widget removal. _( removedWidgetIds ).each( function( removedWidgetId ) { // Using setTimeout so that when moving a widget to another sidebar, // the other sidebars_widgets settings get a chance to update. setTimeout( function() { var removedControl, wasDraggedToAnotherSidebar, inactiveWidgets, removedIdBase, widget, isPresentInAnotherSidebar = false; // Check if the widget is in another sidebar. api.each( function( otherSetting ) { if ( otherSetting.id === self.setting.id || 0 !== otherSetting.id.indexOf( 'sidebars_widgets[' ) || otherSetting.id === 'sidebars_widgets[wp_inactive_widgets]' ) { return; } var otherSidebarWidgets = otherSetting(), i; i = _.indexOf( otherSidebarWidgets, removedWidgetId ); if ( -1 !== i ) { isPresentInAnotherSidebar = true; } } ); // If the widget is present in another sidebar, abort! if ( isPresentInAnotherSidebar ) { return; } removedControl = api.Widgets.getWidgetFormControlForWidget( removedWidgetId ); // Detect if widget control was dragged to another sidebar. wasDraggedToAnotherSidebar = removedControl && $.contains( document, removedControl.container[0] ) && ! $.contains( self.$sectionContent[0], removedControl.container[0] ); // Delete any widget form controls for removed widgets. if ( removedControl && ! wasDraggedToAnotherSidebar ) { api.control.remove( removedControl.id ); removedControl.container.remove(); } // Move widget to inactive widgets sidebar (move it to Trash) if has been previously saved. // This prevents the inactive widgets sidebar from overflowing with throwaway widgets. if ( api.Widgets.savedWidgetIds[removedWidgetId] ) { inactiveWidgets = api.value( 'sidebars_widgets[wp_inactive_widgets]' )().slice(); inactiveWidgets.push( removedWidgetId ); api.value( 'sidebars_widgets[wp_inactive_widgets]' )( _( inactiveWidgets ).unique() ); } // Make old single widget available for adding again. removedIdBase = parseWidgetId( removedWidgetId ).id_base; widget = api.Widgets.availableWidgets.findWhere( { id_base: removedIdBase } ); if ( widget && ! widget.get( 'is_multi' ) ) { widget.set( 'is_disabled', false ); } } ); } ); } ); }, /** * Allow widgets in sidebar to be re-ordered, and for the order to be previewed */ _setupSortable: function() { var self = this; this.isReordering = false; /** * Update widget order setting when controls are re-ordered */ this.$sectionContent.sortable( { items: '> .customize-control-widget_form', handle: '.widget-top', axis: 'y', tolerance: 'pointer', connectWith: '.accordion-section-content:has(.customize-control-sidebar_widgets)', update: function() { var widgetContainerIds = self.$sectionContent.sortable( 'toArray' ), widgetIds; widgetIds = $.map( widgetContainerIds, function( widgetContainerId ) { return $( '#' + widgetContainerId ).find( ':input[name=widget-id]' ).val(); } ); self.setting( widgetIds ); } } ); /** * Expand other Customizer sidebar section when dragging a control widget over it, * allowing the control to be dropped into another section */ this.$controlSection.find( '.accordion-section-title' ).droppable({ accept: '.customize-control-widget_form', over: function() { var section = api.section( self.section.get() ); section.expand({ allowMultiple: true, // Prevent the section being dragged from to be collapsed. completeCallback: function () { // @todo It is not clear when refreshPositions should be called on which sections, or if it is even needed. api.section.each( function ( otherSection ) { if ( otherSection.container.find( '.customize-control-sidebar_widgets' ).length ) { otherSection.container.find( '.accordion-section-content:first' ).sortable( 'refreshPositions' ); } } ); } }); } }); /** * Keyboard-accessible reordering */ this.container.find( '.reorder-toggle' ).on( 'click', function() { self.toggleReordering( ! self.isReordering ); } ); }, /** * Set up UI for adding a new widget */ _setupAddition: function() { var self = this; this.container.find( '.add-new-widget' ).on( 'click', function() { var addNewWidgetBtn = $( this ); if ( self.$sectionContent.hasClass( 'reordering' ) ) { return; } if ( ! $( 'body' ).hasClass( 'adding-widget' ) ) { addNewWidgetBtn.attr( 'aria-expanded', 'true' ); api.Widgets.availableWidgetsPanel.open( self ); } else { addNewWidgetBtn.attr( 'aria-expanded', 'false' ); api.Widgets.availableWidgetsPanel.close(); } } ); }, /** * Add classes to the widget_form controls to assist with styling */ _applyCardinalOrderClassNames: function() { var widgetControls = []; _.each( this.setting(), function ( widgetId ) { var widgetControl = api.Widgets.getWidgetFormControlForWidget( widgetId ); if ( widgetControl ) { widgetControls.push( widgetControl ); } }); if ( 0 === widgetControls.length || ( 1 === api.Widgets.registeredSidebars.length && widgetControls.length <= 1 ) ) { this.container.find( '.reorder-toggle' ).hide(); return; } else { this.container.find( '.reorder-toggle' ).show(); } $( widgetControls ).each( function () { $( this.container ) .removeClass( 'first-widget' ) .removeClass( 'last-widget' ) .find( '.move-widget-down, .move-widget-up' ).prop( 'tabIndex', 0 ); }); _.first( widgetControls ).container .addClass( 'first-widget' ) .find( '.move-widget-up' ).prop( 'tabIndex', -1 ); _.last( widgetControls ).container .addClass( 'last-widget' ) .find( '.move-widget-down' ).prop( 'tabIndex', -1 ); }, /*********************************************************************** * Begin public API methods **********************************************************************/ /** * Enable/disable the reordering UI * * @param {boolean} showOrHide to enable/disable reordering * * @todo We should have a reordering state instead and rename this to onChangeReordering */ toggleReordering: function( showOrHide ) { var addNewWidgetBtn = this.$sectionContent.find( '.add-new-widget' ), reorderBtn = this.container.find( '.reorder-toggle' ), widgetsTitle = this.$sectionContent.find( '.widget-title' ); showOrHide = Boolean( showOrHide ); if ( showOrHide === this.$sectionContent.hasClass( 'reordering' ) ) { return; } this.isReordering = showOrHide; this.$sectionContent.toggleClass( 'reordering', showOrHide ); if ( showOrHide ) { _( this.getWidgetFormControls() ).each( function( formControl ) { formControl.collapse(); } ); addNewWidgetBtn.attr({ 'tabindex': '-1', 'aria-hidden': 'true' }); reorderBtn.attr( 'aria-label', l10n.reorderLabelOff ); wp.a11y.speak( l10n.reorderModeOn ); // Hide widget titles while reordering: title is already in the reorder controls. widgetsTitle.attr( 'aria-hidden', 'true' ); } else { addNewWidgetBtn.removeAttr( 'tabindex aria-hidden' ); reorderBtn.attr( 'aria-label', l10n.reorderLabelOn ); wp.a11y.speak( l10n.reorderModeOff ); widgetsTitle.attr( 'aria-hidden', 'false' ); } }, /** * Get the widget_form Customize controls associated with the current sidebar. * * @since 3.9.0 * @return {wp.customize.controlConstructor.widget_form[]} */ getWidgetFormControls: function() { var formControls = []; _( this.setting() ).each( function( widgetId ) { var settingId = widgetIdToSettingId( widgetId ), formControl = api.control( settingId ); if ( formControl ) { formControls.push( formControl ); } } ); return formControls; }, /** * @param {string} widgetId or an id_base for adding a previously non-existing widget. * @return {Object|false} widget_form control instance, or false on error. */ addWidget: function( widgetId ) { var self = this, controlHtml, $widget, controlType = 'widget_form', controlContainer, controlConstructor, parsedWidgetId = parseWidgetId( widgetId ), widgetNumber = parsedWidgetId.number, widgetIdBase = parsedWidgetId.id_base, widget = api.Widgets.availableWidgets.findWhere( {id_base: widgetIdBase} ), settingId, isExistingWidget, widgetFormControl, sidebarWidgets, settingArgs, setting; if ( ! widget ) { return false; } if ( widgetNumber && ! widget.get( 'is_multi' ) ) { return false; } // Set up new multi widget. if ( widget.get( 'is_multi' ) && ! widgetNumber ) { widget.set( 'multi_number', widget.get( 'multi_number' ) + 1 ); widgetNumber = widget.get( 'multi_number' ); } controlHtml = $( '#widget-tpl-' + widget.get( 'id' ) ).html().trim(); if ( widget.get( 'is_multi' ) ) { controlHtml = controlHtml.replace( /<[^<>]+>/g, function( m ) { return m.replace( /__i__|%i%/g, widgetNumber ); } ); } else { widget.set( 'is_disabled', true ); // Prevent single widget from being added again now. } $widget = $( controlHtml ); controlContainer = $( '<li/>' ) .addClass( 'customize-control' ) .addClass( 'customize-control-' + controlType ) .append( $widget ); // Remove icon which is visible inside the panel. controlContainer.find( '> .widget-icon' ).remove(); if ( widget.get( 'is_multi' ) ) { controlContainer.find( 'input[name="widget_number"]' ).val( widgetNumber ); controlContainer.find( 'input[name="multi_number"]' ).val( widgetNumber ); } widgetId = controlContainer.find( '[name="widget-id"]' ).val(); controlContainer.hide(); // To be slid-down below. settingId = 'widget_' + widget.get( 'id_base' ); if ( widget.get( 'is_multi' ) ) { settingId += '[' + widgetNumber + ']'; } controlContainer.attr( 'id', 'customize-control-' + settingId.replace( /\]/g, '' ).replace( /\[/g, '-' ) ); // Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget). isExistingWidget = api.has( settingId ); if ( ! isExistingWidget ) { settingArgs = { transport: api.Widgets.data.selectiveRefreshableWidgets[ widget.get( 'id_base' ) ] ? 'postMessage' : 'refresh', previewer: this.setting.previewer }; setting = api.create( settingId, settingId, '', settingArgs ); setting.set( {} ); // Mark dirty, changing from '' to {}. } controlConstructor = api.controlConstructor[controlType]; widgetFormControl = new controlConstructor( settingId, { settings: { 'default': settingId }, content: controlContainer, sidebar_id: self.params.sidebar_id, widget_id: widgetId, widget_id_base: widget.get( 'id_base' ), type: controlType, is_new: ! isExistingWidget, width: widget.get( 'width' ), height: widget.get( 'height' ), is_wide: widget.get( 'is_wide' ) } ); api.control.add( widgetFormControl ); // Make sure widget is removed from the other sidebars. api.each( function( otherSetting ) { if ( otherSetting.id === self.setting.id ) { return; } if ( 0 !== otherSetting.id.indexOf( 'sidebars_widgets[' ) ) { return; } var otherSidebarWidgets = otherSetting().slice(), i = _.indexOf( otherSidebarWidgets, widgetId ); if ( -1 !== i ) { otherSidebarWidgets.splice( i ); otherSetting( otherSidebarWidgets ); } } ); // Add widget to this sidebar. sidebarWidgets = this.setting().slice(); if ( -1 === _.indexOf( sidebarWidgets, widgetId ) ) { sidebarWidgets.push( widgetId ); this.setting( sidebarWidgets ); } controlContainer.slideDown( function() { if ( isExistingWidget ) { widgetFormControl.updateWidget( { instance: widgetFormControl.setting() } ); } } ); return widgetFormControl; } } ); // Register models for custom panel, section, and control types. $.extend( api.panelConstructor, { widgets: api.Widgets.WidgetsPanel }); $.extend( api.sectionConstructor, { sidebar: api.Widgets.SidebarSection }); $.extend( api.controlConstructor, { widget_form: api.Widgets.WidgetControl, sidebar_widgets: api.Widgets.SidebarControl }); /** * Init Customizer for widgets. */ api.bind( 'ready', function() { // Set up the widgets panel. api.Widgets.availableWidgetsPanel = new api.Widgets.AvailableWidgetsPanelView({ collection: api.Widgets.availableWidgets }); // Highlight widget control. api.previewer.bind( 'highlight-widget-control', api.Widgets.highlightWidgetFormControl ); // Open and focus widget control. api.previewer.bind( 'focus-widget-control', api.Widgets.focusWidgetFormControl ); } ); /** * Highlight a widget control. * * @param {string} widgetId */ api.Widgets.highlightWidgetFormControl = function( widgetId ) { var control = api.Widgets.getWidgetFormControlForWidget( widgetId ); if ( control ) { control.highlightSectionAndControl(); } }, /** * Focus a widget control. * * @param {string} widgetId */ api.Widgets.focusWidgetFormControl = function( widgetId ) { var control = api.Widgets.getWidgetFormControlForWidget( widgetId ); if ( control ) { control.focus(); } }, /** * Given a widget control, find the sidebar widgets control that contains it. * @param {string} widgetId * @return {Object|null} */ api.Widgets.getSidebarWidgetControlContainingWidget = function( widgetId ) { var foundControl = null; // @todo This can use widgetIdToSettingId(), then pass into wp.customize.control( x ).getSidebarWidgetsControl(). api.control.each( function( control ) { if ( control.params.type === 'sidebar_widgets' && -1 !== _.indexOf( control.setting(), widgetId ) ) { foundControl = control; } } ); return foundControl; }; /** * Given a widget ID for a widget appearing in the preview, get the widget form control associated with it. * * @param {string} widgetId * @return {Object|null} */ api.Widgets.getWidgetFormControlForWidget = function( widgetId ) { var foundControl = null; // @todo We can just use widgetIdToSettingId() here. api.control.each( function( control ) { if ( control.params.type === 'widget_form' && control.params.widget_id === widgetId ) { foundControl = control; } } ); return foundControl; }; /** * Initialize Edit Menu button in Nav Menu widget. */ $( document ).on( 'widget-added', function( event, widgetContainer ) { var parsedWidgetId, widgetControl, navMenuSelect, editMenuButton; parsedWidgetId = parseWidgetId( widgetContainer.find( '> .widget-inside > .form > .widget-id' ).val() ); if ( 'nav_menu' !== parsedWidgetId.id_base ) { return; } widgetControl = api.control( 'widget_nav_menu[' + String( parsedWidgetId.number ) + ']' ); if ( ! widgetControl ) { return; } navMenuSelect = widgetContainer.find( 'select[name*="nav_menu"]' ); editMenuButton = widgetContainer.find( '.edit-selected-nav-menu > button' ); if ( 0 === navMenuSelect.length || 0 === editMenuButton.length ) { return; } navMenuSelect.on( 'change', function() { if ( api.section.has( 'nav_menu[' + navMenuSelect.val() + ']' ) ) { editMenuButton.parent().show(); } else { editMenuButton.parent().hide(); } }); editMenuButton.on( 'click', function() { var section = api.section( 'nav_menu[' + navMenuSelect.val() + ']' ); if ( section ) { focusConstructWithBreadcrumb( section, widgetControl ); } } ); } ); /** * Focus (expand) one construct and then focus on another construct after the first is collapsed. * * This overrides the back button to serve the purpose of breadcrumb navigation. * * @param {wp.customize.Section|wp.customize.Panel|wp.customize.Control} focusConstruct - The object to initially focus. * @param {wp.customize.Section|wp.customize.Panel|wp.customize.Control} returnConstruct - The object to return focus. */ function focusConstructWithBreadcrumb( focusConstruct, returnConstruct ) { focusConstruct.focus(); function onceCollapsed( isExpanded ) { if ( ! isExpanded ) { focusConstruct.expanded.unbind( onceCollapsed ); returnConstruct.focus(); } } focusConstruct.expanded.bind( onceCollapsed ); } /** * @param {string} widgetId * @return {Object} */ function parseWidgetId( widgetId ) { var matches, parsed = { number: null, id_base: null }; matches = widgetId.match( /^(.+)-(\d+)$/ ); if ( matches ) { parsed.id_base = matches[1]; parsed.number = parseInt( matches[2], 10 ); } else { // Likely an old single widget. parsed.id_base = widgetId; } return parsed; } /** * @param {string} widgetId * @return {string} settingId */ function widgetIdToSettingId( widgetId ) { var parsed = parseWidgetId( widgetId ), settingId; settingId = 'widget_' + parsed.id_base; if ( parsed.number ) { settingId += '[' + parsed.number + ']'; } return settingId; } })( window.wp, jQuery ); PK 5N]4��]�P �P inline-edit-post.jsnu �[��� /** * This file contains the functions needed for the inline editing of posts. * * @since 2.7.0 * @output wp-admin/js/inline-edit-post.js */ /* global ajaxurl, typenow, inlineEditPost */ window.wp = window.wp || {}; /** * Manages the quick edit and bulk edit windows for editing posts or pages. * * @namespace inlineEditPost * * @since 2.7.0 * * @type {Object} * * @property {string} type The type of inline editor. * @property {string} what The prefix before the post ID. * */ ( function( $, wp ) { window.inlineEditPost = { /** * Initializes the inline and bulk post editor. * * Binds event handlers to the Escape key to close the inline editor * and to the save and close buttons. Changes DOM to be ready for inline * editing. Adds event handler to bulk edit. * * @since 2.7.0 * * @memberof inlineEditPost * * @return {void} */ init : function(){ var t = this, qeRow = $('#inline-edit'), bulkRow = $('#bulk-edit'); t.type = $('table.widefat').hasClass('pages') ? 'page' : 'post'; // Post ID prefix. t.what = '#post-'; /** * Binds the Escape key to revert the changes and close the quick editor. * * @return {boolean} The result of revert. */ qeRow.on( 'keyup', function(e){ // Revert changes if Escape key is pressed. if ( e.which === 27 ) { return inlineEditPost.revert(); } }); /** * Binds the Escape key to revert the changes and close the bulk editor. * * @return {boolean} The result of revert. */ bulkRow.on( 'keyup', function(e){ // Revert changes if Escape key is pressed. if ( e.which === 27 ) { return inlineEditPost.revert(); } }); /** * Reverts changes and close the quick editor if the cancel button is clicked. * * @return {boolean} The result of revert. */ $( '.cancel', qeRow ).on( 'click', function() { return inlineEditPost.revert(); }); /** * Saves changes in the quick editor if the save(named: update) button is clicked. * * @return {boolean} The result of save. */ $( '.save', qeRow ).on( 'click', function() { return inlineEditPost.save(this); }); /** * If Enter is pressed, and the target is not the cancel button, save the post. * * @return {boolean} The result of save. */ $('td', qeRow).on( 'keydown', function(e){ if ( e.which === 13 && ! $( e.target ).hasClass( 'cancel' ) ) { return inlineEditPost.save(this); } }); /** * Reverts changes and close the bulk editor if the cancel button is clicked. * * @return {boolean} The result of revert. */ $( '.cancel', bulkRow ).on( 'click', function() { return inlineEditPost.revert(); }); /** * Disables the password input field when the private post checkbox is checked. */ $('#inline-edit .inline-edit-private input[value="private"]').on( 'click', function(){ var pw = $('input.inline-edit-password-input'); if ( $(this).prop('checked') ) { pw.val('').prop('disabled', true); } else { pw.prop('disabled', false); } }); /** * Binds click event to the .editinline button which opens the quick editor. */ $( '#the-list' ).on( 'click', '.editinline', function() { $( this ).attr( 'aria-expanded', 'true' ); inlineEditPost.edit( this ); }); // Clone quick edit categories for the bulk editor. var beCategories = $( '#inline-edit fieldset.inline-edit-categories' ).clone(); // Make "id" attributes globally unique. beCategories.find( '*[id]' ).each( function() { this.id = 'bulk-edit-' + this.id; }); $('#bulk-edit').find('fieldset:first').after( beCategories ).siblings( 'fieldset:last' ).prepend( $( '#inline-edit .inline-edit-tags-wrap' ).clone() ); $('select[name="_status"] option[value="future"]', bulkRow).remove(); /** * Adds onclick events to the apply buttons. */ $('#doaction').on( 'click', function(e){ var n, $itemsSelected = $( '#posts-filter .check-column input[type="checkbox"]:checked' ); if ( $itemsSelected.length < 1 ) { return; } t.whichBulkButtonId = $( this ).attr( 'id' ); n = t.whichBulkButtonId.substr( 2 ); if ( 'edit' === $( 'select[name="' + n + '"]' ).val() ) { e.preventDefault(); t.setBulk(); } else if ( $('form#posts-filter tr.inline-editor').length > 0 ) { t.revert(); } }); }, /** * Toggles the quick edit window, hiding it when it's active and showing it when * inactive. * * @since 2.7.0 * * @memberof inlineEditPost * * @param {Object} el Element within a post table row. */ toggle : function(el){ var t = this; $( t.what + t.getId( el ) ).css( 'display' ) === 'none' ? t.revert() : t.edit( el ); }, /** * Creates the bulk editor row to edit multiple posts at once. * * @since 2.7.0 * * @memberof inlineEditPost */ setBulk : function(){ var te = '', type = this.type, c = true; var checkedPosts = $( 'tbody th.check-column input[type="checkbox"]:checked' ); var categories = {}; this.revert(); $( '#bulk-edit td' ).attr( 'colspan', $( 'th:visible, td:visible', '.widefat:first thead' ).length ); // Insert the editor at the top of the table with an empty row above to maintain zebra striping. $('table.widefat tbody').prepend( $('#bulk-edit') ).prepend('<tr class="hidden"></tr>'); $('#bulk-edit').addClass('inline-editor').show(); /** * Create a HTML div with the title and a link(delete-icon) for each selected * post. * * Get the selected posts based on the checked checkboxes in the post table. */ $( 'tbody th.check-column input[type="checkbox"]' ).each( function() { // If the checkbox for a post is selected, add the post to the edit list. if ( $(this).prop('checked') ) { c = false; var id = $( this ).val(), theTitle = $( '#inline_' + id + ' .post_title' ).html() || wp.i18n.__( '(no title)' ), buttonVisuallyHiddenText = wp.i18n.sprintf( /* translators: %s: Post title. */ wp.i18n.__( 'Remove “%s” from Bulk Edit' ), theTitle ); te += '<li class="ntdelitem"><button type="button" id="_' + id + '" class="button-link ntdelbutton"><span class="screen-reader-text">' + buttonVisuallyHiddenText + '</span></button><span class="ntdeltitle" aria-hidden="true">' + theTitle + '</span></li>'; } }); // If no checkboxes where checked, just hide the quick/bulk edit rows. if ( c ) { return this.revert(); } // Populate the list of items to bulk edit. $( '#bulk-titles' ).html( '<ul id="bulk-titles-list" role="list">' + te + '</ul>' ); // Gather up some statistics on which of these checked posts are in which categories. checkedPosts.each( function() { var id = $( this ).val(); var checked = $( '#category_' + id ).text().split( ',' ); checked.map( function( cid ) { categories[ cid ] || ( categories[ cid ] = 0 ); // Just record that this category is checked. categories[ cid ]++; } ); } ); // Compute initial states. $( '.inline-edit-categories input[name="post_category[]"]' ).each( function() { if ( categories[ $( this ).val() ] == checkedPosts.length ) { // If the number of checked categories matches the number of selected posts, then all posts are in this category. $( this ).prop( 'checked', true ); } else if ( categories[ $( this ).val() ] > 0 ) { // If the number is less than the number of selected posts, then it's indeterminate. $( this ).prop( 'indeterminate', true ); if ( ! $( this ).parent().find( 'input[name="indeterminate_post_category[]"]' ).length ) { // Get the term label text. var label = $( this ).parent().text(); // Set indeterminate states for the backend. Add accessible text for indeterminate inputs. $( this ).after( '<input type="hidden" name="indeterminate_post_category[]" value="' + $( this ).val() + '">' ).attr( 'aria-label', label.trim() + ': ' + wp.i18n.__( 'Some selected posts have this category' ) ); } } } ); $( '.inline-edit-categories input[name="post_category[]"]:indeterminate' ).on( 'change', function() { // Remove accessible label text. Remove the indeterminate flags as there was a specific state change. $( this ).removeAttr( 'aria-label' ).parent().find( 'input[name="indeterminate_post_category[]"]' ).remove(); } ); $( '.inline-edit-save button' ).on( 'click', function() { $( '.inline-edit-categories input[name="post_category[]"]' ).prop( 'indeterminate', false ); } ); /** * Binds on click events to handle the list of items to bulk edit. * * @listens click */ $( '#bulk-titles .ntdelbutton' ).click( function() { var $this = $( this ), id = $this.attr( 'id' ).substr( 1 ), $prev = $this.parent().prev().children( '.ntdelbutton' ), $next = $this.parent().next().children( '.ntdelbutton' ); $( 'input#cb-select-all-1, input#cb-select-all-2' ).prop( 'checked', false ); $( 'table.widefat input[value="' + id + '"]' ).prop( 'checked', false ); $( '#_' + id ).parent().remove(); wp.a11y.speak( wp.i18n.__( 'Item removed.' ), 'assertive' ); // Move focus to a proper place when items are removed. if ( $next.length ) { $next.focus(); } else if ( $prev.length ) { $prev.focus(); } else { $( '#bulk-titles-list' ).remove(); inlineEditPost.revert(); wp.a11y.speak( wp.i18n.__( 'All selected items have been removed. Select new items to use Bulk Actions.' ) ); } }); // Enable auto-complete for tags when editing posts. if ( 'post' === type ) { $( 'tr.inline-editor textarea[data-wp-taxonomy]' ).each( function ( i, element ) { /* * While Quick Edit clones the form each time, Bulk Edit always re-uses * the same form. Let's check if an autocomplete instance already exists. */ if ( $( element ).autocomplete( 'instance' ) ) { // jQuery equivalent of `continue` within an `each()` loop. return; } $( element ).wpTagsSuggest(); } ); } // Set initial focus on the Bulk Edit region. $( '#bulk-edit .inline-edit-wrapper' ).attr( 'tabindex', '-1' ).focus(); // Scrolls to the top of the table where the editor is rendered. $('html, body').animate( { scrollTop: 0 }, 'fast' ); }, /** * Creates a quick edit window for the post that has been clicked. * * @since 2.7.0 * * @memberof inlineEditPost * * @param {number|Object} id The ID of the clicked post or an element within a post * table row. * @return {boolean} Always returns false at the end of execution. */ edit : function(id) { var t = this, fields, editRow, rowData, status, pageOpt, pageLevel, nextPage, pageLoop = true, nextLevel, f, val, pw; t.revert(); if ( typeof(id) === 'object' ) { id = t.getId(id); } fields = ['post_title', 'post_name', 'post_author', '_status', 'jj', 'mm', 'aa', 'hh', 'mn', 'ss', 'post_password', 'post_format', 'menu_order', 'page_template']; if ( t.type === 'page' ) { fields.push('post_parent'); } // Add the new edit row with an extra blank row underneath to maintain zebra striping. editRow = $('#inline-edit').clone(true); $( 'td', editRow ).attr( 'colspan', $( 'th:visible, td:visible', '.widefat:first thead' ).length ); // Remove the ID from the copied row and let the `for` attribute reference the hidden ID. $( 'td', editRow ).find('#quick-edit-legend').removeAttr('id'); $( 'td', editRow ).find('p[id^="quick-edit-"]').removeAttr('id'); $(t.what+id).removeClass('is-expanded').hide().after(editRow).after('<tr class="hidden"></tr>'); // Populate fields in the quick edit window. rowData = $('#inline_'+id); if ( !$(':input[name="post_author"] option[value="' + $('.post_author', rowData).text() + '"]', editRow).val() ) { // The post author no longer has edit capabilities, so we need to add them to the list of authors. $(':input[name="post_author"]', editRow).prepend('<option value="' + $('.post_author', rowData).text() + '">' + $('#post-' + id + ' .author').text() + '</option>'); } if ( $( ':input[name="post_author"] option', editRow ).length === 1 ) { $('label.inline-edit-author', editRow).hide(); } for ( f = 0; f < fields.length; f++ ) { val = $('.'+fields[f], rowData); /** * Replaces the image for a Twemoji(Twitter emoji) with it's alternate text. * * @return {string} Alternate text from the image. */ val.find( 'img' ).replaceWith( function() { return this.alt; } ); val = val.text(); $(':input[name="' + fields[f] + '"]', editRow).val( val ); } if ( $( '.comment_status', rowData ).text() === 'open' ) { $( 'input[name="comment_status"]', editRow ).prop( 'checked', true ); } if ( $( '.ping_status', rowData ).text() === 'open' ) { $( 'input[name="ping_status"]', editRow ).prop( 'checked', true ); } if ( $( '.sticky', rowData ).text() === 'sticky' ) { $( 'input[name="sticky"]', editRow ).prop( 'checked', true ); } /** * Creates the select boxes for the categories. */ $('.post_category', rowData).each(function(){ var taxname, term_ids = $(this).text(); if ( term_ids ) { taxname = $(this).attr('id').replace('_'+id, ''); $('ul.'+taxname+'-checklist :checkbox', editRow).val(term_ids.split(',')); } }); /** * Gets all the taxonomies for live auto-fill suggestions when typing the name * of a tag. */ $('.tags_input', rowData).each(function(){ var terms = $(this), taxname = $(this).attr('id').replace('_' + id, ''), textarea = $('textarea.tax_input_' + taxname, editRow), comma = wp.i18n._x( ',', 'tag delimiter' ).trim(); // Ensure the textarea exists. if ( ! textarea.length ) { return; } terms.find( 'img' ).replaceWith( function() { return this.alt; } ); terms = terms.text(); if ( terms ) { if ( ',' !== comma ) { terms = terms.replace(/,/g, comma); } textarea.val(terms); } textarea.wpTagsSuggest(); }); // Handle the post status. var post_date_string = $(':input[name="aa"]').val() + '-' + $(':input[name="mm"]').val() + '-' + $(':input[name="jj"]').val(); post_date_string += ' ' + $(':input[name="hh"]').val() + ':' + $(':input[name="mn"]').val() + ':' + $(':input[name="ss"]').val(); var post_date = new Date( post_date_string ); status = $('._status', rowData).text(); if ( 'future' !== status && Date.now() > post_date ) { $('select[name="_status"] option[value="future"]', editRow).remove(); } else { $('select[name="_status"] option[value="publish"]', editRow).remove(); } pw = $( '.inline-edit-password-input' ).prop( 'disabled', false ); if ( 'private' === status ) { $('input[name="keep_private"]', editRow).prop('checked', true); pw.val( '' ).prop( 'disabled', true ); } // Remove the current page and children from the parent dropdown. pageOpt = $('select[name="post_parent"] option[value="' + id + '"]', editRow); if ( pageOpt.length > 0 ) { pageLevel = pageOpt[0].className.split('-')[1]; nextPage = pageOpt; while ( pageLoop ) { nextPage = nextPage.next('option'); if ( nextPage.length === 0 ) { break; } nextLevel = nextPage[0].className.split('-')[1]; if ( nextLevel <= pageLevel ) { pageLoop = false; } else { nextPage.remove(); nextPage = pageOpt; } } pageOpt.remove(); } $(editRow).attr('id', 'edit-'+id).addClass('inline-editor').show(); $('.ptitle', editRow).trigger( 'focus' ); return false; }, /** * Saves the changes made in the quick edit window to the post. * Ajax saving is only for Quick Edit and not for bulk edit. * * @since 2.7.0 * * @param {number} id The ID for the post that has been changed. * @return {boolean} False, so the form does not submit when pressing * Enter on a focused field. */ save : function(id) { var params, fields, page = $('.post_status_page').val() || ''; if ( typeof(id) === 'object' ) { id = this.getId(id); } $( 'table.widefat .spinner' ).addClass( 'is-active' ); params = { action: 'inline-save', post_type: typenow, post_ID: id, edit_date: 'true', post_status: page }; fields = $('#edit-'+id).find(':input').serialize(); params = fields + '&' + $.param(params); // Make Ajax request. $.post( ajaxurl, params, function(r) { var $errorNotice = $( '#edit-' + id + ' .inline-edit-save .notice-error' ), $error = $errorNotice.find( '.error' ); $( 'table.widefat .spinner' ).removeClass( 'is-active' ); if (r) { if ( -1 !== r.indexOf( '<tr' ) ) { $(inlineEditPost.what+id).siblings('tr.hidden').addBack().remove(); $('#edit-'+id).before(r).remove(); $( inlineEditPost.what + id ).hide().fadeIn( 400, function() { // Move focus back to the Quick Edit button. $( this ) is the row being animated. $( this ).find( '.editinline' ) .attr( 'aria-expanded', 'false' ) .trigger( 'focus' ); wp.a11y.speak( wp.i18n.__( 'Changes saved.' ) ); }); } else { r = r.replace( /<.[^<>]*?>/g, '' ); $errorNotice.removeClass( 'hidden' ); $error.html( r ); wp.a11y.speak( $error.text() ); } } else { $errorNotice.removeClass( 'hidden' ); $error.text( wp.i18n.__( 'Error while saving the changes.' ) ); wp.a11y.speak( wp.i18n.__( 'Error while saving the changes.' ) ); } }, 'html'); // Prevent submitting the form when pressing Enter on a focused field. return false; }, /** * Hides and empties the Quick Edit and/or Bulk Edit windows. * * @since 2.7.0 * * @memberof inlineEditPost * * @return {boolean} Always returns false. */ revert : function(){ var $tableWideFat = $( '.widefat' ), id = $( '.inline-editor', $tableWideFat ).attr( 'id' ); if ( id ) { $( '.spinner', $tableWideFat ).removeClass( 'is-active' ); if ( 'bulk-edit' === id ) { // Hide the bulk editor. $( '#bulk-edit', $tableWideFat ).removeClass( 'inline-editor' ).hide().siblings( '.hidden' ).remove(); $('#bulk-titles').empty(); // Store the empty bulk editor in a hidden element. $('#inlineedit').append( $('#bulk-edit') ); // Move focus back to the Bulk Action button that was activated. $( '#' + inlineEditPost.whichBulkButtonId ).trigger( 'focus' ); } else { // Remove both the inline-editor and its hidden tr siblings. $('#'+id).siblings('tr.hidden').addBack().remove(); id = id.substr( id.lastIndexOf('-') + 1 ); // Show the post row and move focus back to the Quick Edit button. $( this.what + id ).show().find( '.editinline' ) .attr( 'aria-expanded', 'false' ) .trigger( 'focus' ); } } return false; }, /** * Gets the ID for a the post that you want to quick edit from the row in the quick * edit table. * * @since 2.7.0 * * @memberof inlineEditPost * * @param {Object} o DOM row object to get the ID for. * @return {string} The post ID extracted from the table row in the object. */ getId : function(o) { var id = $(o).closest('tr').attr('id'), parts = id.split('-'); return parts[parts.length - 1]; } }; $( function() { inlineEditPost.init(); } ); // Show/hide locks on posts. $( function() { // Set the heartbeat interval to 10 seconds. if ( typeof wp !== 'undefined' && wp.heartbeat ) { wp.heartbeat.interval( 10 ); } }).on( 'heartbeat-tick.wp-check-locked-posts', function( e, data ) { var locked = data['wp-check-locked-posts'] || {}, lockedClass = 'wp-locked'; $('#the-list tr').each( function(i, el) { var key = el.id, row = $(el), lock_data, avatar; if ( locked.hasOwnProperty( key ) ) { if ( ! row.hasClass( lockedClass ) ) { lock_data = locked[key]; row.find('.column-title .locked-text').text( lock_data.text ); row.find('.check-column checkbox').prop('checked', false); if ( lock_data.avatar_src ) { avatar = $( '<img />', { 'class': 'avatar avatar-18 photo', width: 18, height: 18, alt: '', src: lock_data.avatar_src, srcset: lock_data.avatar_src_2x ? lock_data.avatar_src_2x + ' 2x' : undefined } ); row.find('.column-title .locked-avatar').empty().append( avatar ); } row.addClass( lockedClass ); } } else if ( row.hasClass( lockedClass ) ) { row.removeClass( lockedClass ).find( '.locked-info span' ).empty(); } }); }).on( 'heartbeat-send.wp-check-locked-posts', function( e, data ) { var check = []; $('#the-list tr').each( function(i, el) { if ( el.id ) { check.push( el.id ); } }); if ( check.length ) { data['wp-check-locked-posts'] = check; } }); })( jQuery, window.wp ); PK 5N]r�y$ $ auth-app.min.jsnu �[��� /*! This file is auto-generated */ !function(t,s){var p=t("#app_name"),r=t("#approve"),e=t("#reject"),n=p.closest("form"),i={userLogin:s.user_login,successUrl:s.success,rejectUrl:s.reject};r.on("click",function(e){var a=p.val(),o=t('input[name="app_id"]',n).val();e.preventDefault(),r.prop("aria-disabled")||(0===a.length?p.trigger("focus"):(r.prop("aria-disabled",!0).addClass("disabled"),e={name:a},0<o.length&&(e.app_id=o),e=wp.hooks.applyFilters("wp_application_passwords_approve_app_request",e,i),wp.apiRequest({path:"/wp/v2/users/me/application-passwords?_locale=user",method:"POST",data:e}).done(function(e,a,o){wp.hooks.doAction("wp_application_passwords_approve_app_request_success",e,a,o);var a=s.success;a?(o=a+(-1===a.indexOf("?")?"?":"&")+"site_url="+encodeURIComponent(s.site_url)+"&user_login="+encodeURIComponent(s.user_login)+"&password="+encodeURIComponent(e.password),window.location=o):(a=wp.i18n.sprintf('<label for="new-application-password-value">'+wp.i18n.__("Your new password for %s is:")+"</label>","<strong></strong>")+' <input id="new-application-password-value" type="text" class="code" readonly="readonly" value="" />',o=t("<div></div>").attr("role","alert").attr("tabindex",-1).addClass("notice notice-success notice-alt").append(t("<p></p>").addClass("application-password-display").html(a)).append("<p>"+wp.i18n.__("Be sure to save this in a safe location. You will not be able to retrieve it.")+"</p>"),t("strong",o).text(e.name),t("input",o).val(e.password),n.replaceWith(o),o.trigger("focus"))}).fail(function(e,a,o){var s=o,p=null,s=(e.responseJSON&&(p=e.responseJSON).message&&(s=p.message),t("<div></div>").attr("role","alert").addClass("notice notice-error").append(t("<p></p>").text(s)));t("h1").after(s),r.removeProp("aria-disabled",!1).removeClass("disabled"),wp.hooks.doAction("wp_application_passwords_approve_app_request_error",p,a,o,e)})))}),e.on("click",function(e){e.preventDefault(),wp.hooks.doAction("wp_application_passwords_reject_app",i),window.location=s.reject}),n.on("submit",function(e){e.preventDefault()})}(jQuery,authApp);PK 5N]'�8�� � media.min.jsnu �[��� /*! This file is auto-generated */ !function(t){window.findPosts={open:function(n,e){var i=t(".ui-find-overlay");return 0===i.length&&(t("body").append('<div class="ui-find-overlay"></div>'),findPosts.overlay()),i.show(),n&&e&&t("#affected").attr("name",n).val(e),t("#find-posts").show(),t("#find-posts-input").trigger("focus").on("keyup",function(n){27==n.which&&findPosts.close()}),findPosts.send(),!1},close:function(){t("#find-posts-response").empty(),t("#find-posts").hide(),t(".ui-find-overlay").hide()},overlay:function(){t(".ui-find-overlay").on("click",function(){findPosts.close()})},send:function(){var n={ps:t("#find-posts-input").val(),action:"find_posts",_ajax_nonce:t("#_ajax_nonce").val()},e=t(".find-box-search .spinner");e.addClass("is-active"),t.ajax(ajaxurl,{type:"POST",data:n,dataType:"json"}).always(function(){e.removeClass("is-active")}).done(function(n){n.success||t("#find-posts-response").text(wp.i18n.__("An error has occurred. Please reload the page and try again.")),t("#find-posts-response").html(n.data)}).fail(function(){t("#find-posts-response").text(wp.i18n.__("An error has occurred. Please reload the page and try again."))})}},t(function(){var o,n,e=t("#wp-media-grid"),i=new ClipboardJS(".copy-attachment-url.media-library"),s=null;e.length&&window.wp&&window.wp.media&&(n=_wpMediaGridSettings,n=window.wp.media({frame:"manage",container:e,library:n.queryVars}).open(),e.trigger("wp-media-grid-ready",n)),t("#find-posts-submit").on("click",function(n){t('#find-posts-response input[type="radio"]:checked').length||n.preventDefault()}),t("#find-posts .find-box-search :input").on("keypress",function(n){if(13==n.which)return findPosts.send(),!1}),t("#find-posts-search").on("click",findPosts.send),t("#find-posts-close").on("click",findPosts.close),t("#doaction").on("click",function(e){t('select[name="action"]').each(function(){var n=t(this).val();"attach"===n?(e.preventDefault(),findPosts.open()):"delete"!==n||showNotice.warn()||e.preventDefault()})}),t(".find-box-inside").on("click","tr",function(){t(this).find(".found-radio input").prop("checked",!0)}),i.on("success",function(n){var e=t(n.trigger),i=t(".success",e.closest(".copy-to-clipboard-container"));n.clearSelection(),s&&s.addClass("hidden"),clearTimeout(o),i.removeClass("hidden"),o=setTimeout(function(){i.addClass("hidden"),s=null},3e3),s=i,wp.a11y.speak(wp.i18n.__("The file URL has been copied to your clipboard"))})})}(jQuery);PK 5N]��Gc c site-icon.jsnu �[��� /** * Handle the site icon setting in options-general.php. * * @since 6.5.0 * @output wp-admin/js/site-icon.js */ /* global jQuery, wp */ ( function ( $ ) { var $chooseButton = $( '#choose-from-library-button' ), $iconPreview = $( '#site-icon-preview' ), $browserIconPreview = $( '#browser-icon-preview' ), $appIconPreview = $( '#app-icon-preview' ), $hiddenDataField = $( '#site_icon_hidden_field' ), $removeButton = $( '#js-remove-site-icon' ), frame; /** * Calculate image selection options based on the attachment dimensions. * * @since 6.5.0 * * @param {Object} attachment The attachment object representing the image. * @return {Object} The image selection options. */ function calculateImageSelectOptions( attachment ) { var realWidth = attachment.get( 'width' ), realHeight = attachment.get( 'height' ), xInit = 512, yInit = 512, ratio = xInit / yInit, xImg = xInit, yImg = yInit, x1, y1, imgSelectOptions; if ( realWidth / realHeight > ratio ) { yInit = realHeight; xInit = yInit * ratio; } else { xInit = realWidth; yInit = xInit / ratio; } x1 = ( realWidth - xInit ) / 2; y1 = ( realHeight - yInit ) / 2; imgSelectOptions = { aspectRatio: xInit + ':' + yInit, handles: true, keys: true, instance: true, persistent: true, imageWidth: realWidth, imageHeight: realHeight, minWidth: xImg > xInit ? xInit : xImg, minHeight: yImg > yInit ? yInit : yImg, x1: x1, y1: y1, x2: xInit + x1, y2: yInit + y1, }; return imgSelectOptions; } /** * Initializes the media frame for selecting or cropping an image. * * @since 6.5.0 */ $chooseButton.on( 'click', function () { var $el = $( this ); // Create the media frame. frame = wp.media( { button: { // Set the text of the button. text: $el.data( 'update' ), // Don't close, we might need to crop. close: false, }, states: [ new wp.media.controller.Library( { title: $el.data( 'choose-text' ), library: wp.media.query( { type: 'image' } ), date: false, suggestedWidth: $el.data( 'size' ), suggestedHeight: $el.data( 'size' ), } ), new wp.media.controller.SiteIconCropper( { control: { params: { width: $el.data( 'size' ), height: $el.data( 'size' ), }, }, imgSelectOptions: calculateImageSelectOptions, } ), ], } ); frame.on( 'cropped', function ( attachment ) { $hiddenDataField.val( attachment.id ); switchToUpdate( attachment ); frame.close(); // Start over with a frame that is so fresh and so clean clean. frame = null; } ); // When an image is selected, run a callback. frame.on( 'select', function () { // Grab the selected attachment. var attachment = frame.state().get( 'selection' ).first(); if ( attachment.attributes.height === $el.data( 'size' ) && $el.data( 'size' ) === attachment.attributes.width ) { switchToUpdate( attachment.attributes ); frame.close(); // Set the value of the hidden input to the attachment id. $hiddenDataField.val( attachment.id ); } else { frame.setState( 'cropper' ); } } ); frame.open(); } ); /** * Update the UI when a site icon is selected. * * @since 6.5.0 * * @param {array} attributes The attributes for the attachment. */ function switchToUpdate( attributes ) { var i18nAppAlternativeString, i18nBrowserAlternativeString; if ( attributes.alt ) { i18nAppAlternativeString = wp.i18n.sprintf( /* translators: %s: The selected image alt text. */ wp.i18n.__( 'App icon preview: Current image: %s' ), attributes.alt ); i18nBrowserAlternativeString = wp.i18n.sprintf( /* translators: %s: The selected image alt text. */ wp.i18n.__( 'Browser icon preview: Current image: %s' ), attributes.alt ); } else { i18nAppAlternativeString = wp.i18n.sprintf( /* translators: %s: The selected image filename. */ wp.i18n.__( 'App icon preview: The current image has no alternative text. The file name is: %s' ), attributes.filename ); i18nBrowserAlternativeString = wp.i18n.sprintf( /* translators: %s: The selected image filename. */ wp.i18n.__( 'Browser icon preview: The current image has no alternative text. The file name is: %s' ), attributes.filename ); } // Set site-icon-img src and alternative text to app icon preview. $appIconPreview.attr( { src: attributes.url, alt: i18nAppAlternativeString, } ); // Set site-icon-img src and alternative text to browser preview. $browserIconPreview.attr( { src: attributes.url, alt: i18nBrowserAlternativeString, } ); // Remove hidden class from icon preview div and remove button. $iconPreview.removeClass( 'hidden' ); $removeButton.removeClass( 'hidden' ); // Set the global CSS variable for --site-icon-url to the selected image URL. document.documentElement.style.setProperty( '--site-icon-url', 'url(' + attributes.url + ')' ); // If the choose button is not in the update state, swap the classes. if ( $chooseButton.attr( 'data-state' ) !== '1' ) { $chooseButton.attr( { class: $chooseButton.attr( 'data-alt-classes' ), 'data-alt-classes': $chooseButton.attr( 'class' ), 'data-state': '1', } ); } // Swap the text of the choose button. $chooseButton.text( $chooseButton.attr( 'data-update-text' ) ); } /** * Handles the click event of the remove button. * * @since 6.5.0 */ $removeButton.on( 'click', function () { $hiddenDataField.val( 'false' ); $( this ).toggleClass( 'hidden' ); $iconPreview.toggleClass( 'hidden' ); $browserIconPreview.attr( { src: '', alt: '', } ); $appIconPreview.attr( { src: '', alt: '', } ); /** * Resets state to the button, for correct visual style and state. * Updates the text of the button. * Sets focus state to the button. */ $chooseButton .attr( { class: $chooseButton.attr( 'data-alt-classes' ), 'data-alt-classes': $chooseButton.attr( 'class' ), 'data-state': '', } ) .text( $chooseButton.attr( 'data-choose-text' ) ) .trigger( 'focus' ); } ); } )( jQuery ); PK 5N]��\n&� &� post.jsnu �[��� /** * @file Contains all dynamic functionality needed on post and term pages. * * @output wp-admin/js/post.js */ /* global ajaxurl, wpAjax, postboxes, pagenow, tinymce, alert, deleteUserSetting, ClipboardJS */ /* global theList:true, theExtraList:true, getUserSetting, setUserSetting, commentReply, commentsBox */ /* global WPSetThumbnailHTML, wptitlehint */ // Backward compatibility: prevent fatal errors. window.makeSlugeditClickable = window.editPermalink = function(){}; // Make sure the wp object exists. window.wp = window.wp || {}; ( function( $ ) { var titleHasFocus = false, __ = wp.i18n.__; /** * Control loading of comments on the post and term edit pages. * * @type {{st: number, get: commentsBox.get, load: commentsBox.load}} * * @namespace commentsBox */ window.commentsBox = { // Comment offset to use when fetching new comments. st : 0, /** * Fetch comments using Ajax and display them in the box. * * @memberof commentsBox * * @param {number} total Total number of comments for this post. * @param {number} num Optional. Number of comments to fetch, defaults to 20. * @return {boolean} Always returns false. */ get : function(total, num) { var st = this.st, data; if ( ! num ) num = 20; this.st += num; this.total = total; $( '#commentsdiv .spinner' ).addClass( 'is-active' ); data = { 'action' : 'get-comments', 'mode' : 'single', '_ajax_nonce' : $('#add_comment_nonce').val(), 'p' : $('#post_ID').val(), 'start' : st, 'number' : num }; $.post( ajaxurl, data, function(r) { r = wpAjax.parseAjaxResponse(r); $('#commentsdiv .widefat').show(); $( '#commentsdiv .spinner' ).removeClass( 'is-active' ); if ( 'object' == typeof r && r.responses[0] ) { $('#the-comment-list').append( r.responses[0].data ); theList = theExtraList = null; $( 'a[className*=\':\']' ).off(); // If the offset is over the total number of comments we cannot fetch any more, so hide the button. if ( commentsBox.st > commentsBox.total ) $('#show-comments').hide(); else $('#show-comments').show().children('a').text( __( 'Show more comments' ) ); return; } else if ( 1 == r ) { $('#show-comments').text( __( 'No more comments found.' ) ); return; } $('#the-comment-list').append('<tr><td colspan="2">'+wpAjax.broken+'</td></tr>'); } ); return false; }, /** * Load the next batch of comments. * * @memberof commentsBox * * @param {number} total Total number of comments to load. */ load: function(total){ this.st = jQuery('#the-comment-list tr.comment:visible').length; this.get(total); } }; /** * Overwrite the content of the Featured Image postbox * * @param {string} html New HTML to be displayed in the content area of the postbox. * * @global */ window.WPSetThumbnailHTML = function(html){ $('.inside', '#postimagediv').html(html); }; /** * Set the Image ID of the Featured Image * * @param {number} id The post_id of the image to use as Featured Image. * * @global */ window.WPSetThumbnailID = function(id){ var field = $('input[value="_thumbnail_id"]', '#list-table'); if ( field.length > 0 ) { $('#meta\\[' + field.attr('id').match(/[0-9]+/) + '\\]\\[value\\]').text(id); } }; /** * Remove the Featured Image * * @param {string} nonce Nonce to use in the request. * * @global */ window.WPRemoveThumbnail = function(nonce){ $.post( ajaxurl, { action: 'set-post-thumbnail', post_id: $( '#post_ID' ).val(), thumbnail_id: -1, _ajax_nonce: nonce, cookie: encodeURIComponent( document.cookie ) }, /** * Handle server response * * @param {string} str Response, will be '0' when an error occurred otherwise contains link to add Featured Image. */ function(str){ if ( str == '0' ) { alert( __( 'Could not set that as the thumbnail image. Try a different attachment.' ) ); } else { WPSetThumbnailHTML(str); } } ); }; /** * Heartbeat locks. * * Used to lock editing of an object by only one user at a time. * * When the user does not send a heartbeat in a heartbeat-time * the user is no longer editing and another user can start editing. */ $(document).on( 'heartbeat-send.refresh-lock', function( e, data ) { var lock = $('#active_post_lock').val(), post_id = $('#post_ID').val(), send = {}; if ( ! post_id || ! $('#post-lock-dialog').length ) return; send.post_id = post_id; if ( lock ) send.lock = lock; data['wp-refresh-post-lock'] = send; }).on( 'heartbeat-tick.refresh-lock', function( e, data ) { // Post locks: update the lock string or show the dialog if somebody has taken over editing. var received, wrap, avatar; if ( data['wp-refresh-post-lock'] ) { received = data['wp-refresh-post-lock']; if ( received.lock_error ) { // Show "editing taken over" message. wrap = $('#post-lock-dialog'); if ( wrap.length && ! wrap.is(':visible') ) { if ( wp.autosave ) { // Save the latest changes and disable. $(document).one( 'heartbeat-tick', function() { wp.autosave.server.suspend(); wrap.removeClass('saving').addClass('saved'); $(window).off( 'beforeunload.edit-post' ); }); wrap.addClass('saving'); wp.autosave.server.triggerSave(); } if ( received.lock_error.avatar_src ) { avatar = $( '<img />', { 'class': 'avatar avatar-64 photo', width: 64, height: 64, alt: '', src: received.lock_error.avatar_src, srcset: received.lock_error.avatar_src_2x ? received.lock_error.avatar_src_2x + ' 2x' : undefined } ); wrap.find('div.post-locked-avatar').empty().append( avatar ); } wrap.show().find('.currently-editing').text( received.lock_error.text ); wrap.find('.wp-tab-first').trigger( 'focus' ); } } else if ( received.new_lock ) { $('#active_post_lock').val( received.new_lock ); } } }).on( 'before-autosave.update-post-slug', function() { titleHasFocus = document.activeElement && document.activeElement.id === 'title'; }).on( 'after-autosave.update-post-slug', function() { /* * Create slug area only if not already there * and the title field was not focused (user was not typing a title) when autosave ran. */ if ( ! $('#edit-slug-box > *').length && ! titleHasFocus ) { $.post( ajaxurl, { action: 'sample-permalink', post_id: $('#post_ID').val(), new_title: $('#title').val(), samplepermalinknonce: $('#samplepermalinknonce').val() }, function( data ) { if ( data != '-1' ) { $('#edit-slug-box').html(data); } } ); } }); }(jQuery)); /** * Heartbeat refresh nonces. */ (function($) { var check, timeout; /** * Only allow to check for nonce refresh every 30 seconds. */ function schedule() { check = false; window.clearTimeout( timeout ); timeout = window.setTimeout( function(){ check = true; }, 300000 ); } $( function() { schedule(); }).on( 'heartbeat-send.wp-refresh-nonces', function( e, data ) { var post_id, $authCheck = $('#wp-auth-check-wrap'); if ( check || ( $authCheck.length && ! $authCheck.hasClass( 'hidden' ) ) ) { if ( ( post_id = $('#post_ID').val() ) && $('#_wpnonce').val() ) { data['wp-refresh-post-nonces'] = { post_id: post_id }; } } }).on( 'heartbeat-tick.wp-refresh-nonces', function( e, data ) { var nonces = data['wp-refresh-post-nonces']; if ( nonces ) { schedule(); if ( nonces.replace ) { $.each( nonces.replace, function( selector, value ) { $( '#' + selector ).val( value ); }); } if ( nonces.heartbeatNonce ) window.heartbeatSettings.nonce = nonces.heartbeatNonce; } }); }(jQuery)); /** * All post and postbox controls and functionality. */ jQuery( function($) { var stamp, visibility, $submitButtons, updateVisibility, updateText, $textarea = $('#content'), $document = $(document), postId = $('#post_ID').val() || 0, $submitpost = $('#submitpost'), releaseLock = true, $postVisibilitySelect = $('#post-visibility-select'), $timestampdiv = $('#timestampdiv'), $postStatusSelect = $('#post-status-select'), isMac = window.navigator.platform ? window.navigator.platform.indexOf( 'Mac' ) !== -1 : false, copyAttachmentURLClipboard = new ClipboardJS( '.copy-attachment-url.edit-media' ), copyAttachmentURLSuccessTimeout, __ = wp.i18n.__, _x = wp.i18n._x; postboxes.add_postbox_toggles(pagenow); /* * Clear the window name. Otherwise if this is a former preview window where the user navigated to edit another post, * and the first post is still being edited, clicking Preview there will use this window to show the preview. */ window.name = ''; // Post locks: contain focus inside the dialog. If the dialog is shown, focus the first item. $('#post-lock-dialog .notification-dialog').on( 'keydown', function(e) { // Don't do anything when [Tab] is pressed. if ( e.which != 9 ) return; var target = $(e.target); // [Shift] + [Tab] on first tab cycles back to last tab. if ( target.hasClass('wp-tab-first') && e.shiftKey ) { $(this).find('.wp-tab-last').trigger( 'focus' ); e.preventDefault(); // [Tab] on last tab cycles back to first tab. } else if ( target.hasClass('wp-tab-last') && ! e.shiftKey ) { $(this).find('.wp-tab-first').trigger( 'focus' ); e.preventDefault(); } }).filter(':visible').find('.wp-tab-first').trigger( 'focus' ); // Set the heartbeat interval to 10 seconds if post lock dialogs are enabled. if ( wp.heartbeat && $('#post-lock-dialog').length ) { wp.heartbeat.interval( 10 ); } // The form is being submitted by the user. $submitButtons = $submitpost.find( ':submit, a.submitdelete, #post-preview' ).on( 'click.edit-post', function( event ) { var $button = $(this); if ( $button.hasClass('disabled') ) { event.preventDefault(); return; } if ( $button.hasClass('submitdelete') || $button.is( '#post-preview' ) ) { return; } // The form submission can be blocked from JS or by using HTML 5.0 validation on some fields. // Run this only on an actual 'submit'. $('form#post').off( 'submit.edit-post' ).on( 'submit.edit-post', function( event ) { if ( event.isDefaultPrevented() ) { return; } // Stop auto save. if ( wp.autosave ) { wp.autosave.server.suspend(); } if ( typeof commentReply !== 'undefined' ) { /* * Warn the user they have an unsaved comment before submitting * the post data for update. */ if ( ! commentReply.discardCommentChanges() ) { return false; } /* * Close the comment edit/reply form if open to stop the form * action from interfering with the post's form action. */ commentReply.close(); } releaseLock = false; $(window).off( 'beforeunload.edit-post' ); $submitButtons.addClass( 'disabled' ); if ( $button.attr('id') === 'publish' ) { $submitpost.find( '#major-publishing-actions .spinner' ).addClass( 'is-active' ); } else { $submitpost.find( '#minor-publishing .spinner' ).addClass( 'is-active' ); } }); }); // Submit the form saving a draft or an autosave, and show a preview in a new tab. $('#post-preview').on( 'click.post-preview', function( event ) { var $this = $(this), $form = $('form#post'), $previewField = $('input#wp-preview'), target = $this.attr('target') || 'wp-preview', ua = navigator.userAgent.toLowerCase(); event.preventDefault(); if ( $this.hasClass('disabled') ) { return; } if ( wp.autosave ) { wp.autosave.server.tempBlockSave(); } $previewField.val('dopreview'); $form.attr( 'target', target ).trigger( 'submit' ).attr( 'target', '' ); // Workaround for WebKit bug preventing a form submitting twice to the same action. // https://bugs.webkit.org/show_bug.cgi?id=28633 if ( ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1 ) { $form.attr( 'action', function( index, value ) { return value + '?t=' + ( new Date() ).getTime(); }); } $previewField.val(''); }); // Auto save new posts after a title is typed. if ( $( '#auto_draft' ).val() ) { $( '#title' ).on( 'blur', function() { var cancel; if ( ! this.value || $('#edit-slug-box > *').length ) { return; } // Cancel the auto save when the blur was triggered by the user submitting the form. $('form#post').one( 'submit', function() { cancel = true; }); window.setTimeout( function() { if ( ! cancel && wp.autosave ) { wp.autosave.server.triggerSave(); } }, 200 ); }); } $document.on( 'autosave-disable-buttons.edit-post', function() { $submitButtons.addClass( 'disabled' ); }).on( 'autosave-enable-buttons.edit-post', function() { if ( ! wp.heartbeat || ! wp.heartbeat.hasConnectionError() ) { $submitButtons.removeClass( 'disabled' ); } }).on( 'before-autosave.edit-post', function() { $( '.autosave-message' ).text( __( 'Saving Draft…' ) ); }).on( 'after-autosave.edit-post', function( event, data ) { $( '.autosave-message' ).text( data.message ); if ( $( document.body ).hasClass( 'post-new-php' ) ) { $( '.submitbox .submitdelete' ).show(); } }); /* * When the user is trying to load another page, or reloads current page * show a confirmation dialog when there are unsaved changes. */ $( window ).on( 'beforeunload.edit-post', function( event ) { var editor = window.tinymce && window.tinymce.get( 'content' ); var changed = false; if ( wp.autosave ) { changed = wp.autosave.server.postChanged(); } else if ( editor ) { changed = ( ! editor.isHidden() && editor.isDirty() ); } if ( changed ) { event.preventDefault(); // The return string is needed for browser compat. // See https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event. return __( 'The changes you made will be lost if you navigate away from this page.' ); } }).on( 'pagehide.edit-post', function( event ) { if ( ! releaseLock ) { return; } /* * Unload is triggered (by hand) on removing the Thickbox iframe. * Make sure we process only the main document unload. */ if ( event.target && event.target.nodeName != '#document' ) { return; } var postID = $('#post_ID').val(); var postLock = $('#active_post_lock').val(); if ( ! postID || ! postLock ) { return; } var data = { action: 'wp-remove-post-lock', _wpnonce: $('#_wpnonce').val(), post_ID: postID, active_post_lock: postLock }; if ( window.FormData && window.navigator.sendBeacon ) { var formData = new window.FormData(); $.each( data, function( key, value ) { formData.append( key, value ); }); if ( window.navigator.sendBeacon( ajaxurl, formData ) ) { return; } } // Fall back to a synchronous POST request. // See https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon $.post({ async: false, data: data, url: ajaxurl }); }); // Multiple taxonomies. if ( $('#tagsdiv-post_tag').length ) { window.tagBox && window.tagBox.init(); } else { $('.meta-box-sortables').children('div.postbox').each(function(){ if ( this.id.indexOf('tagsdiv-') === 0 ) { window.tagBox && window.tagBox.init(); return false; } }); } // Handle categories. $('.categorydiv').each( function(){ var this_id = $(this).attr('id'), catAddBefore, catAddAfter, taxonomyParts, taxonomy, settingName; taxonomyParts = this_id.split('-'); taxonomyParts.shift(); taxonomy = taxonomyParts.join('-'); settingName = taxonomy + '_tab'; if ( taxonomy == 'category' ) { settingName = 'cats'; } // @todo Move to jQuery 1.3+, support for multiple hierarchical taxonomies, see wp-lists.js. $('a', '#' + taxonomy + '-tabs').on( 'click keyup keydown', function( event ) { var t = $(this).attr('href'); if ( event.type === 'keydown' && event.key === ' ' ) { event.preventDefault(); } if ( ( event.type === 'keyup' && event.key === ' ' ) || ( event.type === 'keydown' && event.key === 'Enter' ) || event.type === 'click' ) { event.preventDefault(); $('#' + taxonomy + '-tabs a').removeAttr( 'aria-selected' ).attr( 'tabindex', '-1' ); $(this).attr( 'aria-selected', 'true' ).removeAttr( 'tabindex' ); $(this).parent().addClass('tabs').siblings('li').removeClass('tabs'); $('#' + taxonomy + '-tabs').siblings('.tabs-panel').hide(); $(t).show(); if ( '#' + taxonomy + '-all' == t ) { deleteUserSetting( settingName ); } else { setUserSetting( settingName, 'pop' ); } } if ( event.type === 'keyup' && ( event.key === 'ArrowRight' || event.key === 'ArrowLeft' ) ) { $(this).attr( 'tabindex', '-1' ); let next = $(this).parent('li').next(); let prev = $(this).parent('li').prev(); if ( next.length > 0 ) { next.find('a').removeAttr( 'tabindex'); next.find('a').trigger( 'focus' ); } else { prev.find('a').removeAttr( 'tabindex'); prev.find('a').trigger( 'focus' ); } } }); if ( getUserSetting( settingName ) ) $('a[href="#' + taxonomy + '-pop"]', '#' + taxonomy + '-tabs').trigger( 'click' ); // Add category button controls. $('#new' + taxonomy).one( 'focus', function() { $( this ).val( '' ).removeClass( 'form-input-tip' ); }); // On [Enter] submit the taxonomy. $('#new' + taxonomy).on( 'keypress', function(event){ if( 13 === event.keyCode ) { event.preventDefault(); $('#' + taxonomy + '-add-submit').trigger( 'click' ); } }); // After submitting a new taxonomy, re-focus the input field. $('#' + taxonomy + '-add-submit').on( 'click', function() { $('#new' + taxonomy).trigger( 'focus' ); }); /** * Before adding a new taxonomy, disable submit button. * * @param {Object} s Taxonomy object which will be added. * * @return {Object} */ catAddBefore = function( s ) { if ( !$('#new'+taxonomy).val() ) { return false; } s.data += '&' + $( ':checked', '#'+taxonomy+'checklist' ).serialize(); $( '#' + taxonomy + '-add-submit' ).prop( 'disabled', true ); return s; }; /** * Re-enable submit button after a taxonomy has been added. * * Re-enable submit button. * If the taxonomy has a parent place the taxonomy underneath the parent. * * @param {Object} r Response. * @param {Object} s Taxonomy data. * * @return {void} */ catAddAfter = function( r, s ) { var sup, drop = $('#new'+taxonomy+'_parent'); $( '#' + taxonomy + '-add-submit' ).prop( 'disabled', false ); if ( 'undefined' != s.parsed.responses[0] && (sup = s.parsed.responses[0].supplemental.newcat_parent) ) { drop.before(sup); drop.remove(); } }; $('#' + taxonomy + 'checklist').wpList({ alt: '', response: taxonomy + '-ajax-response', addBefore: catAddBefore, addAfter: catAddAfter }); // Add new taxonomy button toggles input form visibility. $('#' + taxonomy + '-add-toggle').on( 'click', function( e ) { e.preventDefault(); $('#' + taxonomy + '-adder').toggleClass( 'wp-hidden-children' ); $('a[href="#' + taxonomy + '-all"]', '#' + taxonomy + '-tabs').trigger( 'click' ); $('#new'+taxonomy).trigger( 'focus' ); }); // Sync checked items between "All {taxonomy}" and "Most used" lists. $('#' + taxonomy + 'checklist, #' + taxonomy + 'checklist-pop').on( 'click', 'li.popular-category > label input[type="checkbox"]', function() { var t = $(this), c = t.is(':checked'), id = t.val(); if ( id && t.parents('#taxonomy-'+taxonomy).length ) { $('input#in-' + taxonomy + '-' + id + ', input[id^="in-' + taxonomy + '-' + id + '-"]').prop('checked', c); $('input#in-popular-' + taxonomy + '-' + id).prop('checked', c); } } ); }); // End cats. // Custom Fields postbox. if ( $('#postcustom').length ) { $( '#the-list' ).wpList( { /** * Add current post_ID to request to fetch custom fields * * @ignore * * @param {Object} s Request object. * * @return {Object} Data modified with post_ID attached. */ addBefore: function( s ) { s.data += '&post_id=' + $('#post_ID').val(); return s; }, /** * Show the listing of custom fields after fetching. * * @ignore */ addAfter: function() { $('table#list-table').show(); } }); } /* * Publish Post box (#submitdiv) */ if ( $('#submitdiv').length ) { stamp = $('#timestamp').html(); visibility = $('#post-visibility-display').html(); /** * When the visibility of a post changes sub-options should be shown or hidden. * * @ignore * * @return {void} */ updateVisibility = function() { // Show sticky for public posts. if ( $postVisibilitySelect.find('input:radio:checked').val() != 'public' ) { $('#sticky').prop('checked', false); $('#sticky-span').hide(); } else { $('#sticky-span').show(); } // Show password input field for password protected post. if ( $postVisibilitySelect.find('input:radio:checked').val() != 'password' ) { $('#password-span').hide(); } else { $('#password-span').show(); } }; /** * Make sure all labels represent the current settings. * * @ignore * * @return {boolean} False when an invalid timestamp has been selected, otherwise True. */ updateText = function() { if ( ! $timestampdiv.length ) return true; var attemptedDate, originalDate, currentDate, publishOn, postStatus = $('#post_status'), optPublish = $('option[value="publish"]', postStatus), aa = $('#aa').val(), mm = $('#mm').val(), jj = $('#jj').val(), hh = $('#hh').val(), mn = $('#mn').val(); attemptedDate = new Date( aa, mm - 1, jj, hh, mn ); originalDate = new Date( $('#hidden_aa').val(), $('#hidden_mm').val() -1, $('#hidden_jj').val(), $('#hidden_hh').val(), $('#hidden_mn').val() ); currentDate = new Date( $('#cur_aa').val(), $('#cur_mm').val() -1, $('#cur_jj').val(), $('#cur_hh').val(), $('#cur_mn').val() ); // Catch unexpected date problems. if ( attemptedDate.getFullYear() != aa || (1 + attemptedDate.getMonth()) != mm || attemptedDate.getDate() != jj || attemptedDate.getMinutes() != mn ) { $timestampdiv.find('.timestamp-wrap').addClass('form-invalid'); return false; } else { $timestampdiv.find('.timestamp-wrap').removeClass('form-invalid'); } // Determine what the publish should be depending on the date and post status. if ( attemptedDate > currentDate ) { publishOn = __( 'Schedule for:' ); $('#publish').val( _x( 'Schedule', 'post action/button label' ) ); } else if ( attemptedDate <= currentDate && $('#original_post_status').val() != 'publish' ) { publishOn = __( 'Publish on:' ); $('#publish').val( __( 'Publish' ) ); } else { publishOn = __( 'Published on:' ); $('#publish').val( __( 'Update' ) ); } // If the date is the same, set it to trigger update events. if ( originalDate.toUTCString() == attemptedDate.toUTCString() ) { // Re-set to the current value. $('#timestamp').html(stamp); } else { $('#timestamp').html( '\n' + publishOn + ' <b>' + // translators: 1: Month, 2: Day, 3: Year, 4: Hour, 5: Minute. __( '%1$s %2$s, %3$s at %4$s:%5$s' ) .replace( '%1$s', $( 'option[value="' + mm + '"]', '#mm' ).attr( 'data-text' ) ) .replace( '%2$s', parseInt( jj, 10 ) ) .replace( '%3$s', aa ) .replace( '%4$s', ( '00' + hh ).slice( -2 ) ) .replace( '%5$s', ( '00' + mn ).slice( -2 ) ) + '</b> ' ); } // Add "privately published" to post status when applies. if ( $postVisibilitySelect.find('input:radio:checked').val() == 'private' ) { $('#publish').val( __( 'Update' ) ); if ( 0 === optPublish.length ) { postStatus.append('<option value="publish">' + __( 'Privately Published' ) + '</option>'); } else { optPublish.html( __( 'Privately Published' ) ); } $('option[value="publish"]', postStatus).prop('selected', true); $('#misc-publishing-actions .edit-post-status').hide(); } else { if ( $('#original_post_status').val() == 'future' || $('#original_post_status').val() == 'draft' ) { if ( optPublish.length ) { optPublish.remove(); postStatus.val($('#hidden_post_status').val()); } } else { optPublish.html( __( 'Published' ) ); } if ( postStatus.is(':hidden') ) $('#misc-publishing-actions .edit-post-status').show(); } // Update "Status:" to currently selected status. $('#post-status-display').text( // Remove any potential tags from post status text. wp.sanitize.stripTagsAndEncodeText( $('option:selected', postStatus).text() ) ); // Show or hide the "Save Draft" button. if ( $('option:selected', postStatus).val() == 'private' || $('option:selected', postStatus).val() == 'publish' ) { $('#save-post').hide(); } else { $('#save-post').show(); if ( $('option:selected', postStatus).val() == 'pending' ) { $('#save-post').show().val( __( 'Save as Pending' ) ); } else { $('#save-post').show().val( __( 'Save Draft' ) ); } } return true; }; // Show the visibility options and hide the toggle button when opened. $( '#visibility .edit-visibility').on( 'click', function( e ) { e.preventDefault(); if ( $postVisibilitySelect.is(':hidden') ) { updateVisibility(); $postVisibilitySelect.slideDown( 'fast', function() { $postVisibilitySelect.find( 'input[type="radio"]' ).first().trigger( 'focus' ); } ); $(this).hide(); } }); // Cancel visibility selection area and hide it from view. $postVisibilitySelect.find('.cancel-post-visibility').on( 'click', function( event ) { $postVisibilitySelect.slideUp('fast'); $('#visibility-radio-' + $('#hidden-post-visibility').val()).prop('checked', true); $('#post_password').val($('#hidden-post-password').val()); $('#sticky').prop('checked', $('#hidden-post-sticky').prop('checked')); $('#post-visibility-display').html(visibility); $('#visibility .edit-visibility').show().trigger( 'focus' ); updateText(); event.preventDefault(); }); // Set the selected visibility as current. $postVisibilitySelect.find('.save-post-visibility').on( 'click', function( event ) { // Crazyhorse branch - multiple OK cancels. var visibilityLabel = '', selectedVisibility = $postVisibilitySelect.find('input:radio:checked').val(); $postVisibilitySelect.slideUp('fast'); $('#visibility .edit-visibility').show().trigger( 'focus' ); updateText(); if ( 'public' !== selectedVisibility ) { $('#sticky').prop('checked', false); } switch ( selectedVisibility ) { case 'public': visibilityLabel = $( '#sticky' ).prop( 'checked' ) ? __( 'Public, Sticky' ) : __( 'Public' ); break; case 'private': visibilityLabel = __( 'Private' ); break; case 'password': visibilityLabel = __( 'Password Protected' ); break; } $('#post-visibility-display').text( visibilityLabel ); event.preventDefault(); }); // When the selection changes, update labels. $postVisibilitySelect.find('input:radio').on( 'change', function() { updateVisibility(); }); // Edit publish time click. $timestampdiv.siblings('a.edit-timestamp').on( 'click', function( event ) { if ( $timestampdiv.is( ':hidden' ) ) { $timestampdiv.slideDown( 'fast', function() { $( 'input, select', $timestampdiv.find( '.timestamp-wrap' ) ).first().trigger( 'focus' ); } ); $(this).hide(); } event.preventDefault(); }); // Cancel editing the publish time and hide the settings. $timestampdiv.find('.cancel-timestamp').on( 'click', function( event ) { $timestampdiv.slideUp('fast').siblings('a.edit-timestamp').show().trigger( 'focus' ); $('#mm').val($('#hidden_mm').val()); $('#jj').val($('#hidden_jj').val()); $('#aa').val($('#hidden_aa').val()); $('#hh').val($('#hidden_hh').val()); $('#mn').val($('#hidden_mn').val()); updateText(); event.preventDefault(); }); // Save the changed timestamp. $timestampdiv.find('.save-timestamp').on( 'click', function( event ) { // Crazyhorse branch - multiple OK cancels. if ( updateText() ) { $timestampdiv.slideUp('fast'); $timestampdiv.siblings('a.edit-timestamp').show().trigger( 'focus' ); } event.preventDefault(); }); // Cancel submit when an invalid timestamp has been selected. $('#post').on( 'submit', function( event ) { if ( ! updateText() ) { event.preventDefault(); $timestampdiv.show(); if ( wp.autosave ) { wp.autosave.enableButtons(); } $( '#publishing-action .spinner' ).removeClass( 'is-active' ); } }); // Post Status edit click. $postStatusSelect.siblings('a.edit-post-status').on( 'click', function( event ) { if ( $postStatusSelect.is( ':hidden' ) ) { $postStatusSelect.slideDown( 'fast', function() { $postStatusSelect.find('select').trigger( 'focus' ); } ); $(this).hide(); } event.preventDefault(); }); // Save the Post Status changes and hide the options. $postStatusSelect.find('.save-post-status').on( 'click', function( event ) { $postStatusSelect.slideUp( 'fast' ).siblings( 'a.edit-post-status' ).show().trigger( 'focus' ); updateText(); event.preventDefault(); }); // Cancel Post Status editing and hide the options. $postStatusSelect.find('.cancel-post-status').on( 'click', function( event ) { $postStatusSelect.slideUp( 'fast' ).siblings( 'a.edit-post-status' ).show().trigger( 'focus' ); $('#post_status').val( $('#hidden_post_status').val() ); updateText(); event.preventDefault(); }); } /** * Handle the editing of the post_name. Create the required HTML elements and * update the changes via Ajax. * * @global * * @return {void} */ function editPermalink() { var i, slug_value, slug_label, $el, revert_e, c = 0, real_slug = $('#post_name'), revert_slug = real_slug.val(), permalink = $( '#sample-permalink' ), permalinkOrig = permalink.html(), permalinkInner = $( '#sample-permalink a' ).html(), buttons = $('#edit-slug-buttons'), buttonsOrig = buttons.html(), full = $('#editable-post-name-full'); // Deal with Twemoji in the post-name. full.find( 'img' ).replaceWith( function() { return this.alt; } ); full = full.html(); permalink.html( permalinkInner ); // Save current content to revert to when cancelling. $el = $( '#editable-post-name' ); revert_e = $el.html(); buttons.html( '<button type="button" class="save button button-compact">' + __( 'OK' ) + '</button> ' + '<button type="button" class="cancel button-link">' + __( 'Cancel' ) + '</button>' ); // Save permalink changes. buttons.children( '.save' ).on( 'click', function() { var new_slug = $el.children( 'input' ).val(); if ( new_slug == $('#editable-post-name-full').text() ) { buttons.children('.cancel').trigger( 'click' ); return; } $.post( ajaxurl, { action: 'sample-permalink', post_id: postId, new_slug: new_slug, new_title: $('#title').val(), samplepermalinknonce: $('#samplepermalinknonce').val() }, function(data) { var box = $('#edit-slug-box'); box.html(data); if (box.hasClass('hidden')) { box.fadeIn('fast', function () { box.removeClass('hidden'); }); } buttons.html(buttonsOrig); permalink.html(permalinkOrig); real_slug.val(new_slug); $( '.edit-slug' ).trigger( 'focus' ); wp.a11y.speak( __( 'Permalink saved' ) ); } ); }); // Cancel editing of permalink. buttons.children( '.cancel' ).on( 'click', function() { $('#view-post-btn').show(); $el.html(revert_e); buttons.html(buttonsOrig); permalink.html(permalinkOrig); real_slug.val(revert_slug); $( '.edit-slug' ).trigger( 'focus' ); }); // If more than 1/4th of 'full' is '%', make it empty. for ( i = 0; i < full.length; ++i ) { if ( '%' == full.charAt(i) ) c++; } slug_value = ( c > full.length / 4 ) ? '' : full; slug_label = __( 'URL Slug' ); $el.html( '<label for="new-post-slug" class="screen-reader-text">' + slug_label + '</label>' + '<input type="text" id="new-post-slug" value="' + slug_value + '" autocomplete="off" spellcheck="false" />' ).children( 'input' ).on( 'keydown', function( e ) { var key = e.which; // On [Enter], just save the new slug, don't save the post. if ( 13 === key ) { e.preventDefault(); buttons.children( '.save' ).trigger( 'click' ); } // On [Esc] cancel the editing. if ( 27 === key ) { buttons.children( '.cancel' ).trigger( 'click' ); } } ).on( 'keyup', function() { real_slug.val( this.value ); }).trigger( 'focus' ); } $( '#titlediv' ).on( 'click', '.edit-slug', function() { editPermalink(); }); /** * Adds screen reader text to the title label when needed. * * Use the 'screen-reader-text' class to emulate a placeholder attribute * and hide the label when entering a value. * * @param {string} id Optional. HTML ID to add the screen reader helper text to. * * @global * * @return {void} */ window.wptitlehint = function( id ) { id = id || 'title'; var title = $( '#' + id ), titleprompt = $( '#' + id + '-prompt-text' ); if ( '' === title.val() ) { titleprompt.removeClass( 'screen-reader-text' ); } title.on( 'input', function() { if ( '' === this.value ) { titleprompt.removeClass( 'screen-reader-text' ); return; } titleprompt.addClass( 'screen-reader-text' ); } ); }; wptitlehint(); // Resize the WYSIWYG and plain text editors. ( function() { var editor, offset, mce, $handle = $('#post-status-info'), $postdivrich = $('#postdivrich'); // If there are no textareas or we are on a touch device, we can't do anything. if ( ! $textarea.length || 'ontouchstart' in window ) { // Hide the resize handle. $('#content-resize-handle').hide(); return; } /** * Handle drag event. * * @param {Object} event Event containing details about the drag. */ function dragging( event ) { if ( $postdivrich.hasClass( 'wp-editor-expand' ) ) { return; } if ( mce ) { editor.theme.resizeTo( null, offset + event.pageY ); } else { $textarea.height( Math.max( 50, offset + event.pageY ) ); } event.preventDefault(); } /** * When the dragging stopped make sure we return focus and do a confidence check on the height. */ function endDrag() { var height, toolbarHeight; if ( $postdivrich.hasClass( 'wp-editor-expand' ) ) { return; } if ( mce ) { editor.focus(); toolbarHeight = parseInt( $( '#wp-content-editor-container .mce-toolbar-grp' ).height(), 10 ); if ( toolbarHeight < 10 || toolbarHeight > 200 ) { toolbarHeight = 30; } height = parseInt( $('#content_ifr').css('height'), 10 ) + toolbarHeight - 28; } else { $textarea.trigger( 'focus' ); height = parseInt( $textarea.css('height'), 10 ); } $document.off( '.wp-editor-resize' ); // Confidence check: normalize height to stay within acceptable ranges. if ( height && height > 50 && height < 5000 ) { setUserSetting( 'ed_size', height ); } } $handle.on( 'mousedown.wp-editor-resize', function( event ) { if ( typeof tinymce !== 'undefined' ) { editor = tinymce.get('content'); } if ( editor && ! editor.isHidden() ) { mce = true; offset = $('#content_ifr').height() - event.pageY; } else { mce = false; offset = $textarea.height() - event.pageY; $textarea.trigger( 'blur' ); } $document.on( 'mousemove.wp-editor-resize', dragging ) .on( 'mouseup.wp-editor-resize mouseleave.wp-editor-resize', endDrag ); event.preventDefault(); }).on( 'mouseup.wp-editor-resize', endDrag ); })(); // TinyMCE specific handling of Post Format changes to reflect in the editor. if ( typeof tinymce !== 'undefined' ) { // When changing post formats, change the editor body class. $( '#post-formats-select input.post-format' ).on( 'change.set-editor-class', function() { var editor, body, format = this.id; if ( format && $( this ).prop( 'checked' ) && ( editor = tinymce.get( 'content' ) ) ) { body = editor.getBody(); body.className = body.className.replace( /\bpost-format-[^ ]+/, '' ); editor.dom.addClass( body, format == 'post-format-0' ? 'post-format-standard' : format ); $( document ).trigger( 'editor-classchange' ); } }); // When changing page template, change the editor body class. $( '#page_template' ).on( 'change.set-editor-class', function() { var editor, body, pageTemplate = $( this ).val() || ''; pageTemplate = pageTemplate.substr( pageTemplate.lastIndexOf( '/' ) + 1, pageTemplate.length ) .replace( /\.php$/, '' ) .replace( /\./g, '-' ); if ( pageTemplate && ( editor = tinymce.get( 'content' ) ) ) { body = editor.getBody(); body.className = body.className.replace( /\bpage-template-[^ ]+/, '' ); editor.dom.addClass( body, 'page-template-' + pageTemplate ); $( document ).trigger( 'editor-classchange' ); } }); } // Save on pressing [Ctrl]/[Command] + [S] in the Text editor. $textarea.on( 'keydown.wp-autosave', function( event ) { // Key [S] has code 83. if ( event.which === 83 ) { if ( event.shiftKey || event.altKey || ( isMac && ( ! event.metaKey || event.ctrlKey ) ) || ( ! isMac && ! event.ctrlKey ) ) { return; } wp.autosave && wp.autosave.server.triggerSave(); event.preventDefault(); } }); // If the last status was auto-draft and the save is triggered, edit the current URL. if ( $( '#original_post_status' ).val() === 'auto-draft' && window.history.replaceState ) { var location; $( '#publish' ).on( 'click', function() { location = window.location.href; location += ( location.indexOf( '?' ) !== -1 ) ? '&' : '?'; location += 'wp-post-new-reload=true'; window.history.replaceState( null, null, location ); }); } /** * Copies the attachment URL in the Edit Media page to the clipboard. * * @since 5.5.0 * * @param {MouseEvent} event A click event. * * @return {void} */ copyAttachmentURLClipboard.on( 'success', function( event ) { var triggerElement = $( event.trigger ), successElement = $( '.success', triggerElement.closest( '.copy-to-clipboard-container' ) ); // Clear the selection and move focus back to the trigger. event.clearSelection(); // Show success visual feedback. clearTimeout( copyAttachmentURLSuccessTimeout ); successElement.removeClass( 'hidden' ); // Hide success visual feedback after 3 seconds since last success. copyAttachmentURLSuccessTimeout = setTimeout( function() { successElement.addClass( 'hidden' ); }, 3000 ); // Handle success audible feedback. wp.a11y.speak( __( 'The file URL has been copied to your clipboard' ) ); } ); } ); /** * TinyMCE word count display */ ( function( $, counter ) { $( function() { var $content = $( '#content' ), $count = $( '#wp-word-count' ).find( '.word-count' ), prevCount = 0, contentEditor; /** * Get the word count from TinyMCE and display it */ function update() { var text, count; if ( ! contentEditor || contentEditor.isHidden() ) { text = $content.val(); } else { text = contentEditor.getContent( { format: 'raw' } ); } count = counter.count( text ); if ( count !== prevCount ) { $count.text( count ); } prevCount = count; } /** * Bind the word count update triggers. * * When a node change in the main TinyMCE editor has been triggered. * When a key has been released in the plain text content editor. */ $( document ).on( 'tinymce-editor-init', function( event, editor ) { if ( editor.id !== 'content' ) { return; } contentEditor = editor; editor.on( 'nodechange keyup', _.debounce( update, 1000 ) ); } ); $content.on( 'input keyup', _.debounce( update, 1000 ) ); update(); } ); } )( jQuery, new wp.utils.WordCounter() ); PK 5N]�:�86 86 site-health.jsnu �[��� /** * Interactions used by the Site Health modules in WordPress. * * @output wp-admin/js/site-health.js */ /* global ajaxurl, ClipboardJS, SiteHealth, wp */ jQuery( function( $ ) { var __ = wp.i18n.__, _n = wp.i18n._n, sprintf = wp.i18n.sprintf, clipboard = new ClipboardJS( '.site-health-copy-buttons .copy-button' ), isStatusTab = $( '.health-check-body.health-check-status-tab' ).length, isDebugTab = $( '.health-check-body.health-check-debug-tab' ).length, pathsSizesSection = $( '#health-check-accordion-block-wp-paths-sizes' ), menuCounterWrapper = $( '#adminmenu .site-health-counter' ), menuCounter = $( '#adminmenu .site-health-counter .count' ), successTimeout; // Debug information copy section. clipboard.on( 'success', function( e ) { var triggerElement = $( e.trigger ), successElement = $( '.success', triggerElement.closest( 'div' ) ); // Clear the selection and move focus back to the trigger. e.clearSelection(); // Show success visual feedback. clearTimeout( successTimeout ); successElement.removeClass( 'hidden' ); // Hide success visual feedback after 3 seconds since last success. successTimeout = setTimeout( function() { successElement.addClass( 'hidden' ); }, 3000 ); // Handle success audible feedback. wp.a11y.speak( __( 'Site information has been copied to your clipboard.' ) ); } ); // Accordion handling in various areas. $( '.health-check-accordion' ).on( 'click', '.health-check-accordion-trigger', function() { var isExpanded = ( 'true' === $( this ).attr( 'aria-expanded' ) ); if ( $( this ).prop( 'id' ) ) { window.location.hash = $( this ).prop( 'id' ); } if ( isExpanded ) { $( this ).attr( 'aria-expanded', 'false' ); $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', true ); } else { $( this ).attr( 'aria-expanded', 'true' ); $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false ); } } ); /* global setTimeout */ wp.domReady( function() { // Get hash from query string and open the related accordion. var hash = window.location.hash; if ( hash ) { var requestedPanel = $( hash ); if ( requestedPanel.is( '.health-check-accordion-trigger' ) ) { requestedPanel.trigger( 'click' ); } } } ); // Site Health test handling. $( '.site-health-view-passed' ).on( 'click', function() { var goodIssuesWrapper = $( '#health-check-issues-good' ); goodIssuesWrapper.toggleClass( 'hidden' ); $( this ).attr( 'aria-expanded', ! goodIssuesWrapper.hasClass( 'hidden' ) ); } ); /** * Validates the Site Health test result format. * * @since 5.6.0 * * @param {Object} issue * * @return {boolean} */ function validateIssueData( issue ) { // Expected minimum format of a valid SiteHealth test response. var minimumExpected = { test: 'string', label: 'string', description: 'string' }, passed = true, key, value, subKey, subValue; // If the issue passed is not an object, return a `false` state early. if ( 'object' !== typeof( issue ) ) { return false; } // Loop over expected data and match the data types. for ( key in minimumExpected ) { value = minimumExpected[ key ]; if ( 'object' === typeof( value ) ) { for ( subKey in value ) { subValue = value[ subKey ]; if ( 'undefined' === typeof( issue[ key ] ) || 'undefined' === typeof( issue[ key ][ subKey ] ) || subValue !== typeof( issue[ key ][ subKey ] ) ) { passed = false; } } } else { if ( 'undefined' === typeof( issue[ key ] ) || value !== typeof( issue[ key ] ) ) { passed = false; } } } return passed; } /** * Appends a new issue to the issue list. * * @since 5.2.0 * * @param {Object} issue The issue data. */ function appendIssue( issue ) { var template = wp.template( 'health-check-issue' ), issueWrapper = $( '#health-check-issues-' + issue.status ), heading, count; /* * Validate the issue data format before using it. * If the output is invalid, discard it. */ if ( ! validateIssueData( issue ) ) { return false; } SiteHealth.site_status.issues[ issue.status ]++; count = SiteHealth.site_status.issues[ issue.status ]; // If no test name is supplied, append a placeholder for markup references. if ( typeof issue.test === 'undefined' ) { issue.test = issue.status + count; } if ( 'critical' === issue.status ) { heading = sprintf( _n( '%s critical issue', '%s critical issues', count ), '<span class="issue-count">' + count + '</span>' ); } else if ( 'recommended' === issue.status ) { heading = sprintf( _n( '%s recommended improvement', '%s recommended improvements', count ), '<span class="issue-count">' + count + '</span>' ); } else if ( 'good' === issue.status ) { heading = sprintf( _n( '%s item with no issues detected', '%s items with no issues detected', count ), '<span class="issue-count">' + count + '</span>' ); } if ( heading ) { $( '.site-health-issue-count-title', issueWrapper ).html( heading ); } menuCounter.text( SiteHealth.site_status.issues.critical ); if ( 0 < parseInt( SiteHealth.site_status.issues.critical, 0 ) ) { $( '#health-check-issues-critical' ).removeClass( 'hidden' ); menuCounterWrapper.removeClass( 'count-0' ); } else { menuCounterWrapper.addClass( 'count-0' ); } if ( 0 < parseInt( SiteHealth.site_status.issues.recommended, 0 ) ) { $( '#health-check-issues-recommended' ).removeClass( 'hidden' ); } $( '.issues', '#health-check-issues-' + issue.status ).append( template( issue ) ); } /** * Updates site health status indicator as asynchronous tests are run and returned. * * @since 5.2.0 */ function recalculateProgression() { var r, c, pct; var $progress = $( '.site-health-progress' ); var $wrapper = $progress.closest( '.site-health-progress-wrapper' ); var $progressLabel = $( '.site-health-progress-label', $wrapper ); var $circle = $( '.site-health-progress svg #bar' ); var totalTests = parseInt( SiteHealth.site_status.issues.good, 0 ) + parseInt( SiteHealth.site_status.issues.recommended, 0 ) + ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 ); var failedTests = ( parseInt( SiteHealth.site_status.issues.recommended, 0 ) * 0.5 ) + ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 ); var val = 100 - Math.ceil( ( failedTests / totalTests ) * 100 ); if ( 0 === totalTests ) { $progress.addClass( 'hidden' ); return; } $wrapper.removeClass( 'loading' ); r = $circle.attr( 'r' ); c = Math.PI * ( r * 2 ); if ( 0 > val ) { val = 0; } if ( 100 < val ) { val = 100; } pct = ( ( 100 - val ) / 100 ) * c + 'px'; $circle.css( { strokeDashoffset: pct } ); if ( 80 <= val && 0 === parseInt( SiteHealth.site_status.issues.critical, 0 ) ) { $wrapper.addClass( 'green' ).removeClass( 'orange' ); $progressLabel.text( __( 'Good' ) ); announceTestsProgression( 'good' ); } else { $wrapper.addClass( 'orange' ).removeClass( 'green' ); $progressLabel.text( __( 'Should be improved' ) ); announceTestsProgression( 'improvable' ); } if ( isStatusTab ) { $.post( ajaxurl, { 'action': 'health-check-site-status-result', '_wpnonce': SiteHealth.nonce.site_status_result, 'counts': SiteHealth.site_status.issues } ); if ( 100 === val ) { $( '.site-status-all-clear' ).removeClass( 'hide' ); $( '.site-status-has-issues' ).addClass( 'hide' ); } } } /** * Queues the next asynchronous test when we're ready to run it. * * @since 5.2.0 */ function maybeRunNextAsyncTest() { var doCalculation = true; if ( 1 <= SiteHealth.site_status.async.length ) { $.each( SiteHealth.site_status.async, function() { var data = { 'action': 'health-check-' + this.test.replace( '_', '-' ), '_wpnonce': SiteHealth.nonce.site_status }; if ( this.completed ) { return true; } doCalculation = false; this.completed = true; if ( 'undefined' !== typeof( this.has_rest ) && this.has_rest ) { wp.apiRequest( { url: wp.url.addQueryArgs( this.test, { _locale: 'user' } ), headers: this.headers } ) .done( function( response ) { /** This filter is documented in wp-admin/includes/class-wp-site-health.php */ appendIssue( wp.hooks.applyFilters( 'site_status_test_result', response ) ); } ) .fail( function( response ) { var description; if ( 'undefined' !== typeof( response.responseJSON ) && 'undefined' !== typeof( response.responseJSON.message ) ) { description = response.responseJSON.message; } else { description = __( 'No details available' ); } addFailedSiteHealthCheckNotice( this.url, description ); } ) .always( function() { maybeRunNextAsyncTest(); } ); } else { $.post( ajaxurl, data ).done( function( response ) { /** This filter is documented in wp-admin/includes/class-wp-site-health.php */ appendIssue( wp.hooks.applyFilters( 'site_status_test_result', response.data ) ); } ).fail( function( response ) { var description; if ( 'undefined' !== typeof( response.responseJSON ) && 'undefined' !== typeof( response.responseJSON.message ) ) { description = response.responseJSON.message; } else { description = __( 'No details available' ); } addFailedSiteHealthCheckNotice( this.url, description ); } ).always( function() { maybeRunNextAsyncTest(); } ); } return false; } ); } if ( doCalculation ) { recalculateProgression(); } } /** * Add the details of a failed asynchronous test to the list of test results. * * @since 5.6.0 */ function addFailedSiteHealthCheckNotice( url, description ) { var issue; issue = { 'status': 'recommended', 'label': __( 'A test is unavailable' ), 'badge': { 'color': 'red', 'label': __( 'Unavailable' ) }, 'description': '<p>' + url + '</p><p>' + description + '</p>', 'actions': '' }; /** This filter is documented in wp-admin/includes/class-wp-site-health.php */ appendIssue( wp.hooks.applyFilters( 'site_status_test_result', issue ) ); } if ( 'undefined' !== typeof SiteHealth ) { if ( 0 === SiteHealth.site_status.direct.length && 0 === SiteHealth.site_status.async.length ) { recalculateProgression(); } else { SiteHealth.site_status.issues = { 'good': 0, 'recommended': 0, 'critical': 0 }; } if ( 0 < SiteHealth.site_status.direct.length ) { $.each( SiteHealth.site_status.direct, function() { appendIssue( this ); } ); } if ( 0 < SiteHealth.site_status.async.length ) { maybeRunNextAsyncTest(); } else { recalculateProgression(); } } function getDirectorySizes() { var timestamp = ( new Date().getTime() ); // After 3 seconds announce that we're still waiting for directory sizes. var timeout = window.setTimeout( function() { announceTestsProgression( 'waiting-for-directory-sizes' ); }, 3000 ); wp.apiRequest( { path: '/wp-site-health/v1/directory-sizes' } ).done( function( response ) { updateDirSizes( response || {} ); } ).always( function() { var delay = ( new Date().getTime() ) - timestamp; $( '.health-check-wp-paths-sizes.spinner' ).css( 'visibility', 'hidden' ); if ( delay > 3000 ) { /* * We have announced that we're waiting. * Announce that we're ready after giving at least 3 seconds * for the first announcement to be read out, or the two may collide. */ if ( delay > 6000 ) { delay = 0; } else { delay = 6500 - delay; } window.setTimeout( function() { recalculateProgression(); }, delay ); } else { // Cancel the announcement. window.clearTimeout( timeout ); } $( document ).trigger( 'site-health-info-dirsizes-done' ); } ); } function updateDirSizes( data ) { var copyButton = $( 'button.button.copy-button' ); var clipboardText = copyButton.attr( 'data-clipboard-text' ); $.each( data, function( name, value ) { var text = value.debug || value.size; if ( typeof text !== 'undefined' ) { clipboardText = clipboardText.replace( name + ': loading...', name + ': ' + text ); } } ); copyButton.attr( 'data-clipboard-text', clipboardText ); pathsSizesSection.find( 'td[class]' ).each( function( i, element ) { var td = $( element ); var name = td.attr( 'class' ); if ( data.hasOwnProperty( name ) && data[ name ].size ) { td.text( data[ name ].size ); } } ); } if ( isDebugTab ) { if ( pathsSizesSection.length ) { getDirectorySizes(); } else { recalculateProgression(); } } // Trigger a class toggle when the extended menu button is clicked. $( '.health-check-offscreen-nav-wrapper' ).on( 'click', function() { $( this ).toggleClass( 'visible' ); } ); /** * Announces to assistive technologies the tests progression status. * * @since 6.4.0 * * @param {string} type The type of message to be announced. * * @return {void} */ function announceTestsProgression( type ) { // Only announce the messages in the Site Health pages. if ( 'site-health' !== SiteHealth.screen ) { return; } switch ( type ) { case 'good': wp.a11y.speak( __( 'All site health tests have finished running. Your site is looking good.' ) ); break; case 'improvable': wp.a11y.speak( __( 'All site health tests have finished running. There are items that should be addressed.' ) ); break; case 'waiting-for-directory-sizes': wp.a11y.speak( __( 'Running additional tests... please wait.' ) ); break; default: return; } } } ); PK 5N]��'f�<