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