[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/js/ -> customize-preview.js (source)

   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 );


Generated: Fri Apr 19 01:00:02 2024 Cross-referenced by PHPXref 0.7.1