From a415c29efee45520ae252d2aa28f1083a521cd7b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 09:56:49 +0200 Subject: Adding upstream version 6.4.3+dfsg1. Signed-off-by: Daniel Baumann --- wp-includes/js/customize-base.js | 994 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 994 insertions(+) create mode 100644 wp-includes/js/customize-base.js (limited to 'wp-includes/js/customize-base.js') diff --git a/wp-includes/js/customize-base.js b/wp-includes/js/customize-base.js new file mode 100644 index 0000000..4afaa94 --- /dev/null +++ b/wp-includes/js/customize-base.js @@ -0,0 +1,994 @@ +/** + * @output wp-includes/js/customize-base.js + */ + +/** @namespace wp */ +window.wp = window.wp || {}; + +(function( exports, $ ){ + var api = {}, ctor, inherits, + slice = Array.prototype.slice; + + // Shared empty constructor function to aid in prototype-chain creation. + ctor = function() {}; + + /** + * Helper function to correctly set up the prototype chain, for subclasses. + * Similar to `goog.inherits`, but uses a hash of prototype properties and + * class properties to be extended. + * + * @param object parent Parent class constructor to inherit from. + * @param object protoProps Properties to apply to the prototype for use as class instance properties. + * @param object staticProps Properties to apply directly to the class constructor. + * @return child The subclassed constructor. + */ + inherits = function( parent, protoProps, staticProps ) { + var child; + + /* + * The constructor function for the new subclass is either defined by you + * (the "constructor" property in your `extend` definition), or defaulted + * by us to simply call `super()`. + */ + if ( protoProps && protoProps.hasOwnProperty( 'constructor' ) ) { + child = protoProps.constructor; + } else { + child = function() { + /* + * Storing the result `super()` before returning the value + * prevents a bug in Opera where, if the constructor returns + * a function, Opera will reject the return value in favor of + * the original object. This causes all sorts of trouble. + */ + var result = parent.apply( this, arguments ); + return result; + }; + } + + // Inherit class (static) properties from parent. + $.extend( child, parent ); + + // Set the prototype chain to inherit from `parent`, + // without calling `parent`'s constructor function. + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + + // Add prototype properties (instance properties) to the subclass, + // if supplied. + if ( protoProps ) { + $.extend( child.prototype, protoProps ); + } + + // Add static properties to the constructor function, if supplied. + if ( staticProps ) { + $.extend( child, staticProps ); + } + + // Correctly set child's `prototype.constructor`. + child.prototype.constructor = child; + + // Set a convenience property in case the parent's prototype is needed later. + child.__super__ = parent.prototype; + + return child; + }; + + /** + * Base class for object inheritance. + */ + api.Class = function( applicator, argsArray, options ) { + var magic, args = arguments; + + if ( applicator && argsArray && api.Class.applicator === applicator ) { + args = argsArray; + $.extend( this, options || {} ); + } + + magic = this; + + /* + * If the class has a method called "instance", + * the return value from the class' constructor will be a function that + * calls the "instance" method. + * + * It is also an object that has properties and methods inside it. + */ + if ( this.instance ) { + magic = function() { + return magic.instance.apply( magic, arguments ); + }; + + $.extend( magic, this ); + } + + magic.initialize.apply( magic, args ); + return magic; + }; + + /** + * Creates a subclass of the class. + * + * @param object protoProps Properties to apply to the prototype. + * @param object staticProps Properties to apply directly to the class. + * @return child The subclass. + */ + api.Class.extend = function( protoProps, staticProps ) { + var child = inherits( this, protoProps, staticProps ); + child.extend = this.extend; + return child; + }; + + api.Class.applicator = {}; + + /** + * Initialize a class instance. + * + * Override this function in a subclass as needed. + */ + api.Class.prototype.initialize = function() {}; + + /* + * Checks whether a given instance extended a constructor. + * + * The magic surrounding the instance parameter causes the instanceof + * keyword to return inaccurate results; it defaults to the function's + * prototype instead of the constructor chain. Hence this function. + */ + api.Class.prototype.extended = function( constructor ) { + var proto = this; + + while ( typeof proto.constructor !== 'undefined' ) { + if ( proto.constructor === constructor ) { + return true; + } + if ( typeof proto.constructor.__super__ === 'undefined' ) { + return false; + } + proto = proto.constructor.__super__; + } + return false; + }; + + /** + * An events manager object, offering the ability to bind to and trigger events. + * + * Used as a mixin. + */ + api.Events = { + trigger: function( id ) { + if ( this.topics && this.topics[ id ] ) { + this.topics[ id ].fireWith( this, slice.call( arguments, 1 ) ); + } + return this; + }, + + bind: function( id ) { + this.topics = this.topics || {}; + this.topics[ id ] = this.topics[ id ] || $.Callbacks(); + this.topics[ id ].add.apply( this.topics[ id ], slice.call( arguments, 1 ) ); + return this; + }, + + unbind: function( id ) { + if ( this.topics && this.topics[ id ] ) { + this.topics[ id ].remove.apply( this.topics[ id ], slice.call( arguments, 1 ) ); + } + return this; + } + }; + + /** + * Observable values that support two-way binding. + * + * @memberOf wp.customize + * @alias wp.customize.Value + * + * @constructor + */ + api.Value = api.Class.extend(/** @lends wp.customize.Value.prototype */{ + /** + * @param {mixed} initial The initial value. + * @param {Object} options + */ + initialize: function( initial, options ) { + this._value = initial; // @todo Potentially change this to a this.set() call. + this.callbacks = $.Callbacks(); + this._dirty = false; + + $.extend( this, options || {} ); + + this.set = this.set.bind( this ); + }, + + /* + * Magic. Returns a function that will become the instance. + * Set to null to prevent the instance from extending a function. + */ + instance: function() { + return arguments.length ? this.set.apply( this, arguments ) : this.get(); + }, + + /** + * Get the value. + * + * @return {mixed} + */ + get: function() { + return this._value; + }, + + /** + * Set the value and trigger all bound callbacks. + * + * @param {Object} to New value. + */ + set: function( to ) { + var from = this._value; + + to = this._setter.apply( this, arguments ); + to = this.validate( to ); + + // Bail if the sanitized value is null or unchanged. + if ( null === to || _.isEqual( from, to ) ) { + return this; + } + + this._value = to; + this._dirty = true; + + this.callbacks.fireWith( this, [ to, from ] ); + + return this; + }, + + _setter: function( to ) { + return to; + }, + + setter: function( callback ) { + var from = this.get(); + this._setter = callback; + // Temporarily clear value so setter can decide if it's valid. + this._value = null; + this.set( from ); + return this; + }, + + resetSetter: function() { + this._setter = this.constructor.prototype._setter; + this.set( this.get() ); + return this; + }, + + validate: function( value ) { + return value; + }, + + /** + * Bind a function to be invoked whenever the value changes. + * + * @param {...Function} A function, or multiple functions, to add to the callback stack. + */ + bind: function() { + this.callbacks.add.apply( this.callbacks, arguments ); + return this; + }, + + /** + * Unbind a previously bound function. + * + * @param {...Function} A function, or multiple functions, to remove from the callback stack. + */ + unbind: function() { + this.callbacks.remove.apply( this.callbacks, arguments ); + return this; + }, + + link: function() { // values* + var set = this.set; + $.each( arguments, function() { + this.bind( set ); + }); + return this; + }, + + unlink: function() { // values* + var set = this.set; + $.each( arguments, function() { + this.unbind( set ); + }); + return this; + }, + + sync: function() { // values* + var that = this; + $.each( arguments, function() { + that.link( this ); + this.link( that ); + }); + return this; + }, + + unsync: function() { // values* + var that = this; + $.each( arguments, function() { + that.unlink( this ); + this.unlink( that ); + }); + return this; + } + }); + + /** + * A collection of observable values. + * + * @memberOf wp.customize + * @alias wp.customize.Values + * + * @constructor + * @augments wp.customize.Class + * @mixes wp.customize.Events + */ + api.Values = api.Class.extend(/** @lends wp.customize.Values.prototype */{ + + /** + * The default constructor for items of the collection. + * + * @type {object} + */ + defaultConstructor: api.Value, + + initialize: function( options ) { + $.extend( this, options || {} ); + + this._value = {}; + this._deferreds = {}; + }, + + /** + * Get the instance of an item from the collection if only ID is specified. + * + * If more than one argument is supplied, all are expected to be IDs and + * the last to be a function callback that will be invoked when the requested + * items are available. + * + * @see {api.Values.when} + * + * @param {string} id ID of the item. + * @param {...} Zero or more IDs of items to wait for and a callback + * function to invoke when they're available. Optional. + * @return {mixed} The item instance if only one ID was supplied. + * A Deferred Promise object if a callback function is supplied. + */ + instance: function( id ) { + if ( arguments.length === 1 ) { + return this.value( id ); + } + + return this.when.apply( this, arguments ); + }, + + /** + * Get the instance of an item. + * + * @param {string} id The ID of the item. + * @return {[type]} [description] + */ + value: function( id ) { + return this._value[ id ]; + }, + + /** + * Whether the collection has an item with the given ID. + * + * @param {string} id The ID of the item to look for. + * @return {boolean} + */ + has: function( id ) { + return typeof this._value[ id ] !== 'undefined'; + }, + + /** + * Add an item to the collection. + * + * @param {string|wp.customize.Class} item - The item instance to add, or the ID for the instance to add. + * When an ID string is supplied, then itemObject must be provided. + * @param {wp.customize.Class} [itemObject] - The item instance when the first argument is an ID string. + * @return {wp.customize.Class} The new item's instance, or an existing instance if already added. + */ + add: function( item, itemObject ) { + var collection = this, id, instance; + if ( 'string' === typeof item ) { + id = item; + instance = itemObject; + } else { + if ( 'string' !== typeof item.id ) { + throw new Error( 'Unknown key' ); + } + id = item.id; + instance = item; + } + + if ( collection.has( id ) ) { + return collection.value( id ); + } + + collection._value[ id ] = instance; + instance.parent = collection; + + // Propagate a 'change' event on an item up to the collection. + if ( instance.extended( api.Value ) ) { + instance.bind( collection._change ); + } + + collection.trigger( 'add', instance ); + + // If a deferred object exists for this item, + // resolve it. + if ( collection._deferreds[ id ] ) { + collection._deferreds[ id ].resolve(); + } + + return collection._value[ id ]; + }, + + /** + * Create a new item of the collection using the collection's default constructor + * and store it in the collection. + * + * @param {string} id The ID of the item. + * @param {mixed} value Any extra arguments are passed into the item's initialize method. + * @return {mixed} The new item's instance. + */ + create: function( id ) { + return this.add( id, new this.defaultConstructor( api.Class.applicator, slice.call( arguments, 1 ) ) ); + }, + + /** + * Iterate over all items in the collection invoking the provided callback. + * + * @param {Function} callback Function to invoke. + * @param {Object} context Object context to invoke the function with. Optional. + */ + each: function( callback, context ) { + context = typeof context === 'undefined' ? this : context; + + $.each( this._value, function( key, obj ) { + callback.call( context, obj, key ); + }); + }, + + /** + * Remove an item from the collection. + * + * @param {string} id The ID of the item to remove. + */ + remove: function( id ) { + var value = this.value( id ); + + if ( value ) { + + // Trigger event right before the element is removed from the collection. + this.trigger( 'remove', value ); + + if ( value.extended( api.Value ) ) { + value.unbind( this._change ); + } + delete value.parent; + } + + delete this._value[ id ]; + delete this._deferreds[ id ]; + + // Trigger removed event after the item has been eliminated from the collection. + if ( value ) { + this.trigger( 'removed', value ); + } + }, + + /** + * Runs a callback once all requested values exist. + * + * when( ids*, [callback] ); + * + * For example: + * when( id1, id2, id3, function( value1, value2, value3 ) {} ); + * + * @return $.Deferred.promise(); + */ + when: function() { + var self = this, + ids = slice.call( arguments ), + dfd = $.Deferred(); + + // If the last argument is a callback, bind it to .done(). + if ( typeof ids[ ids.length - 1 ] === 'function' ) { + dfd.done( ids.pop() ); + } + + /* + * Create a stack of deferred objects for each item that is not + * yet available, and invoke the supplied callback when they are. + */ + $.when.apply( $, $.map( ids, function( id ) { + if ( self.has( id ) ) { + return; + } + + /* + * The requested item is not available yet, create a deferred + * object to resolve when it becomes available. + */ + return self._deferreds[ id ] = self._deferreds[ id ] || $.Deferred(); + })).done( function() { + var values = $.map( ids, function( id ) { + return self( id ); + }); + + // If a value is missing, we've used at least one expired deferred. + // Call Values.when again to generate a new deferred. + if ( values.length !== ids.length ) { + // ids.push( callback ); + self.when.apply( self, ids ).done( function() { + dfd.resolveWith( self, values ); + }); + return; + } + + dfd.resolveWith( self, values ); + }); + + return dfd.promise(); + }, + + /** + * A helper function to propagate a 'change' event from an item + * to the collection itself. + */ + _change: function() { + this.parent.trigger( 'change', this ); + } + }); + + // Create a global events bus on the Customizer. + $.extend( api.Values.prototype, api.Events ); + + + /** + * Cast a string to a jQuery collection if it isn't already. + * + * @param {string|jQuery collection} element + */ + api.ensure = function( element ) { + return typeof element === 'string' ? $( element ) : element; + }; + + /** + * An observable value that syncs with an element. + * + * Handles inputs, selects, and textareas by default. + * + * @memberOf wp.customize + * @alias wp.customize.Element + * + * @constructor + * @augments wp.customize.Value + * @augments wp.customize.Class + */ + api.Element = api.Value.extend(/** @lends wp.customize.Element */{ + initialize: function( element, options ) { + var self = this, + synchronizer = api.Element.synchronizer.html, + type, update, refresh; + + this.element = api.ensure( element ); + this.events = ''; + + if ( this.element.is( 'input, select, textarea' ) ) { + type = this.element.prop( 'type' ); + this.events += ' change input'; + synchronizer = api.Element.synchronizer.val; + + if ( this.element.is( 'input' ) && api.Element.synchronizer[ type ] ) { + synchronizer = api.Element.synchronizer[ type ]; + } + } + + api.Value.prototype.initialize.call( this, null, $.extend( options || {}, synchronizer ) ); + this._value = this.get(); + + update = this.update; + refresh = this.refresh; + + this.update = function( to ) { + if ( to !== refresh.call( self ) ) { + update.apply( this, arguments ); + } + }; + this.refresh = function() { + self.set( refresh.call( self ) ); + }; + + this.bind( this.update ); + this.element.on( this.events, this.refresh ); + }, + + find: function( selector ) { + return $( selector, this.element ); + }, + + refresh: function() {}, + + update: function() {} + }); + + api.Element.synchronizer = {}; + + $.each( [ 'html', 'val' ], function( index, method ) { + api.Element.synchronizer[ method ] = { + update: function( to ) { + this.element[ method ]( to ); + }, + refresh: function() { + return this.element[ method ](); + } + }; + }); + + api.Element.synchronizer.checkbox = { + update: function( to ) { + this.element.prop( 'checked', to ); + }, + refresh: function() { + return this.element.prop( 'checked' ); + } + }; + + api.Element.synchronizer.radio = { + update: function( to ) { + this.element.filter( function() { + return this.value === to; + }).prop( 'checked', true ); + }, + refresh: function() { + return this.element.filter( ':checked' ).val(); + } + }; + + $.support.postMessage = !! window.postMessage; + + /** + * A communicator for sending data from one window to another over postMessage. + * + * @memberOf wp.customize + * @alias wp.customize.Messenger + * + * @constructor + * @augments wp.customize.Class + * @mixes wp.customize.Events + */ + api.Messenger = api.Class.extend(/** @lends wp.customize.Messenger.prototype */{ + /** + * Create a new Value. + * + * @param {string} key Unique identifier. + * @param {mixed} initial Initial value. + * @param {mixed} options Options hash. Optional. + * @return {Value} Class instance of the Value. + */ + add: function( key, initial, options ) { + return this[ key ] = new api.Value( initial, options ); + }, + + /** + * Initialize Messenger. + * + * @param {Object} params - Parameters to configure the messenger. + * {string} params.url - The URL to communicate with. + * {window} params.targetWindow - The window instance to communicate with. Default window.parent. + * {string} params.channel - If provided, will send the channel with each message and only accept messages a matching channel. + * @param {Object} options - Extend any instance parameter or method with this object. + */ + initialize: function( params, options ) { + // Target the parent frame by default, but only if a parent frame exists. + var defaultTarget = window.parent === window ? null : window.parent; + + $.extend( this, options || {} ); + + this.add( 'channel', params.channel ); + this.add( 'url', params.url || '' ); + this.add( 'origin', this.url() ).link( this.url ).setter( function( to ) { + var urlParser = document.createElement( 'a' ); + urlParser.href = to; + // Port stripping needed by IE since it adds to host but not to event.origin. + return urlParser.protocol + '//' + urlParser.host.replace( /:(80|443)$/, '' ); + }); + + // First add with no value. + this.add( 'targetWindow', null ); + // This avoids SecurityErrors when setting a window object in x-origin iframe'd scenarios. + this.targetWindow.set = function( to ) { + var from = this._value; + + to = this._setter.apply( this, arguments ); + to = this.validate( to ); + + if ( null === to || from === to ) { + return this; + } + + this._value = to; + this._dirty = true; + + this.callbacks.fireWith( this, [ to, from ] ); + + return this; + }; + // Now set it. + this.targetWindow( params.targetWindow || defaultTarget ); + + + /* + * Since we want jQuery to treat the receive function as unique + * to this instance, we give the function a new guid. + * + * This will prevent every Messenger's receive function from being + * unbound when calling $.off( 'message', this.receive ); + */ + this.receive = this.receive.bind( this ); + this.receive.guid = $.guid++; + + $( window ).on( 'message', this.receive ); + }, + + destroy: function() { + $( window ).off( 'message', this.receive ); + }, + + /** + * Receive data from the other window. + * + * @param {jQuery.Event} event Event with embedded data. + */ + receive: function( event ) { + var message; + + event = event.originalEvent; + + if ( ! this.targetWindow || ! this.targetWindow() ) { + return; + } + + // Check to make sure the origin is valid. + if ( this.origin() && event.origin !== this.origin() ) { + return; + } + + // Ensure we have a string that's JSON.parse-able. + if ( typeof event.data !== 'string' || event.data[0] !== '{' ) { + return; + } + + message = JSON.parse( event.data ); + + // Check required message properties. + if ( ! message || ! message.id || typeof message.data === 'undefined' ) { + return; + } + + // Check if channel names match. + if ( ( message.channel || this.channel() ) && this.channel() !== message.channel ) { + return; + } + + this.trigger( message.id, message.data ); + }, + + /** + * Send data to the other window. + * + * @param {string} id The event name. + * @param {Object} data Data. + */ + send: function( id, data ) { + var message; + + data = typeof data === 'undefined' ? null : data; + + if ( ! this.url() || ! this.targetWindow() ) { + return; + } + + message = { id: id, data: data }; + if ( this.channel() ) { + message.channel = this.channel(); + } + + this.targetWindow().postMessage( JSON.stringify( message ), this.origin() ); + } + }); + + // Add the Events mixin to api.Messenger. + $.extend( api.Messenger.prototype, api.Events ); + + /** + * Notification. + * + * @class + * @augments wp.customize.Class + * @since 4.6.0 + * + * @memberOf wp.customize + * @alias wp.customize.Notification + * + * @param {string} code - The error code. + * @param {object} params - Params. + * @param {string} params.message=null - The error message. + * @param {string} [params.type=error] - The notification type. + * @param {boolean} [params.fromServer=false] - Whether the notification was server-sent. + * @param {string} [params.setting=null] - The setting ID that the notification is related to. + * @param {*} [params.data=null] - Any additional data. + */ + api.Notification = api.Class.extend(/** @lends wp.customize.Notification.prototype */{ + + /** + * Template function for rendering the notification. + * + * This will be populated with template option or else it will be populated with template from the ID. + * + * @since 4.9.0 + * @var {Function} + */ + template: null, + + /** + * ID for the template to render the notification. + * + * @since 4.9.0 + * @var {string} + */ + templateId: 'customize-notification', + + /** + * Additional class names to add to the notification container. + * + * @since 4.9.0 + * @var {string} + */ + containerClasses: '', + + /** + * Initialize notification. + * + * @since 4.9.0 + * + * @param {string} code - Notification code. + * @param {Object} params - Notification parameters. + * @param {string} params.message - Message. + * @param {string} [params.type=error] - Type. + * @param {string} [params.setting] - Related setting ID. + * @param {Function} [params.template] - Function for rendering template. If not provided, this will come from templateId. + * @param {string} [params.templateId] - ID for template to render the notification. + * @param {string} [params.containerClasses] - Additional class names to add to the notification container. + * @param {boolean} [params.dismissible] - Whether the notification can be dismissed. + */ + initialize: function( code, params ) { + var _params; + this.code = code; + _params = _.extend( + { + message: null, + type: 'error', + fromServer: false, + data: null, + setting: null, + template: null, + dismissible: false, + containerClasses: '' + }, + params + ); + delete _params.code; + _.extend( this, _params ); + }, + + /** + * Render the notification. + * + * @since 4.9.0 + * + * @return {jQuery} Notification container element. + */ + render: function() { + var notification = this, container, data; + if ( ! notification.template ) { + notification.template = wp.template( notification.templateId ); + } + data = _.extend( {}, notification, { + alt: notification.parent && notification.parent.alt + } ); + container = $( notification.template( data ) ); + + if ( notification.dismissible ) { + container.find( '.notice-dismiss' ).on( 'click keydown', function( event ) { + if ( 'keydown' === event.type && 13 !== event.which ) { + return; + } + + if ( notification.parent ) { + notification.parent.remove( notification.code ); + } else { + container.remove(); + } + }); + } + + return container; + } + }); + + // The main API object is also a collection of all customizer settings. + api = $.extend( new api.Values(), api ); + + /** + * Get all customize settings. + * + * @alias wp.customize.get + * + * @return {Object} + */ + api.get = function() { + var result = {}; + + this.each( function( obj, key ) { + result[ key ] = obj.get(); + }); + + return result; + }; + + /** + * Utility function namespace + * + * @namespace wp.customize.utils + */ + api.utils = {}; + + /** + * Parse query string. + * + * @since 4.7.0 + * @access public + * + * @alias wp.customize.utils.parseQueryString + * + * @param {string} queryString Query string. + * @return {Object} Parsed query string. + */ + api.utils.parseQueryString = function parseQueryString( queryString ) { + var queryParams = {}; + _.each( queryString.split( '&' ), function( pair ) { + var parts, key, value; + parts = pair.split( '=', 2 ); + if ( ! parts[0] ) { + return; + } + key = decodeURIComponent( parts[0].replace( /\+/g, ' ' ) ); + key = key.replace( / /g, '_' ); // What PHP does. + if ( _.isUndefined( parts[1] ) ) { + value = null; + } else { + value = decodeURIComponent( parts[1].replace( /\+/g, ' ' ) ); + } + queryParams[ key ] = value; + } ); + return queryParams; + }; + + /** + * Expose the API publicly on window.wp.customize + * + * @namespace wp.customize + */ + exports.customize = api; +})( wp, jQuery ); -- cgit v1.2.3