X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/wordpress.git/blobdiff_plain/fa11948979fd6a4ea5705dc613b239699a459db3..9e77185fafaf4e60e2b73821e0e4b9b1a11fb85f:/wp-admin/js/customize-controls.js diff --git a/wp-admin/js/customize-controls.js b/wp-admin/js/customize-controls.js index f780db7c..85b171db 100644 --- a/wp-admin/js/customize-controls.js +++ b/wp-admin/js/customize-controls.js @@ -1,15 +1,18 @@ +/* globals _wpCustomizeHeader, _wpMediaViewsL10n */ (function( exports, $ ){ var api = wp.customize; - /* + /** + * @constructor + * @augments wp.customize.Value + * @augments wp.customize.Class + * * @param options * - previewer - The Previewer instance to sync with. * - transport - The transport to use for previewing. Supports 'refresh' and 'postMessage'. */ api.Setting = api.Value.extend({ initialize: function( id, value, options ) { - var element; - api.Value.prototype.initialize.call( this, value, options ); this.id = id; @@ -27,6 +30,10 @@ } }); + /** + * @constructor + * @augments wp.customize.Class + */ api.Control = api.Class.extend({ initialize: function( id, options ) { var control = this, @@ -38,6 +45,7 @@ this.id = id; this.selector = '#customize-control-' + id.replace( /\]/g, '' ).replace( /\[/g, '-' ); this.container = $( this.selector ); + this.active = new api.Value( this.params.active ); settings = $.map( this.params.settings, function( value ) { return value; @@ -80,23 +88,45 @@ element.set( setting() ); }); }); + + control.active.bind( function ( active ) { + control.toggle( active ); + } ); + control.toggle( control.active() ); }, + /** + * @abstract + */ ready: function() {}, + /** + * Callback for change to the control's active state. + * + * Override function for custom behavior for the control being active/inactive. + * + * @param {Boolean} active + */ + toggle: function ( active ) { + if ( active ) { + this.container.slideDown(); + } else { + this.container.slideUp(); + } + }, + dropdownInit: function() { - var control = this, - statuses = this.container.find('.dropdown-status'), - params = this.params, - update = function( to ) { - if ( typeof to === 'string' && params.statuses && params.statuses[ to ] ) + var control = this, + statuses = this.container.find('.dropdown-status'), + params = this.params, + toggleFreeze = false, + update = function( to ) { + if ( typeof to === 'string' && params.statuses && params.statuses[ to ] ) statuses.html( params.statuses[ to ] ).show(); else statuses.hide(); }; - var toggleFreeze = false; - // Support the .dropdown class to open/close complex elements this.container.on( 'click keydown', '.dropdown', function( event ) { if ( event.type === 'keydown' && 13 !== event.which ) // enter @@ -122,22 +152,32 @@ } }); + /** + * @constructor + * @augments wp.customize.Control + * @augments wp.customize.Class + */ api.ColorControl = api.Control.extend({ ready: function() { var control = this, picker = this.container.find('.color-picker-hex'); picker.val( control.setting() ).wpColorPicker({ - change: function( event, options ) { + change: function() { control.setting.set( picker.wpColorPicker('color') ); - }, - clear: function() { - control.setting.set( false ); - } + }, + clear: function() { + control.setting.set( false ); + } }); } }); + /** + * @constructor + * @augments wp.customize.Control + * @augments wp.customize.Class + */ api.UploadControl = api.Control.extend({ ready: function() { var control = this; @@ -191,13 +231,19 @@ } }); + /** + * @constructor + * @augments wp.customize.UploadControl + * @augments wp.customize.Control + * @augments wp.customize.Class + */ api.ImageControl = api.UploadControl.extend({ ready: function() { var control = this, panels; this.uploader = { - init: function( up ) { + init: function() { var fallback, button; if ( this.supports.dragdrop ) @@ -309,18 +355,238 @@ } }); + /** + * @constructor + * @augments wp.customize.Control + * @augments wp.customize.Class + */ + api.HeaderControl = api.Control.extend({ + ready: function() { + this.btnRemove = $('#customize-control-header_image .actions .remove'); + this.btnNew = $('#customize-control-header_image .actions .new'); + + _.bindAll(this, 'openMedia', 'removeImage'); + + this.btnNew.on( 'click', this.openMedia ); + this.btnRemove.on( 'click', this.removeImage ); + + api.HeaderTool.currentHeader = new api.HeaderTool.ImageModel(); + + new api.HeaderTool.CurrentView({ + model: api.HeaderTool.currentHeader, + el: '.current .container' + }); + + new api.HeaderTool.ChoiceListView({ + collection: api.HeaderTool.UploadsList = new api.HeaderTool.ChoiceList(), + el: '.choices .uploaded .list' + }); + + new api.HeaderTool.ChoiceListView({ + collection: api.HeaderTool.DefaultsList = new api.HeaderTool.DefaultsList(), + el: '.choices .default .list' + }); + + api.HeaderTool.combinedList = api.HeaderTool.CombinedList = new api.HeaderTool.CombinedList([ + api.HeaderTool.UploadsList, + api.HeaderTool.DefaultsList + ]); + }, + + /** + * Returns a set of options, computed from the attached image data and + * theme-specific data, to be fed to the imgAreaSelect plugin in + * wp.media.view.Cropper. + * + * @param {wp.media.model.Attachment} attachment + * @param {wp.media.controller.Cropper} controller + * @returns {Object} Options + */ + calculateImageSelectOptions: function(attachment, controller) { + var xInit = parseInt(_wpCustomizeHeader.data.width, 10), + yInit = parseInt(_wpCustomizeHeader.data.height, 10), + flexWidth = !! parseInt(_wpCustomizeHeader.data['flex-width'], 10), + flexHeight = !! parseInt(_wpCustomizeHeader.data['flex-height'], 10), + ratio, xImg, yImg, realHeight, realWidth, + imgSelectOptions; + + realWidth = attachment.get('width'); + realHeight = attachment.get('height'); + + this.headerImage = new api.HeaderTool.ImageModel(); + this.headerImage.set({ + themeWidth: xInit, + themeHeight: yInit, + themeFlexWidth: flexWidth, + themeFlexHeight: flexHeight, + imageWidth: realWidth, + imageHeight: realHeight + }); + + controller.set( 'canSkipCrop', ! this.headerImage.shouldBeCropped() ); + + ratio = xInit / yInit; + xImg = realWidth; + yImg = realHeight; + + if ( xImg / yImg > ratio ) { + yInit = yImg; + xInit = yInit * ratio; + } else { + xInit = xImg; + yInit = xInit / ratio; + } + + imgSelectOptions = { + handles: true, + keys: true, + instance: true, + persistent: true, + imageWidth: realWidth, + imageHeight: realHeight, + x1: 0, + y1: 0, + x2: xInit, + y2: yInit + }; + + if (flexHeight === false && flexWidth === false) { + imgSelectOptions.aspectRatio = xInit + ':' + yInit; + } + if (flexHeight === false ) { + imgSelectOptions.maxHeight = yInit; + } + if (flexWidth === false ) { + imgSelectOptions.maxWidth = xInit; + } + + return imgSelectOptions; + }, + + /** + * Sets up and opens the Media Manager in order to select an image. + * Depending on both the size of the image and the properties of the + * current theme, a cropping step after selection may be required or + * skippable. + * + * @param {event} event + */ + openMedia: function(event) { + var l10n = _wpMediaViewsL10n; + + event.preventDefault(); + + this.frame = wp.media({ + button: { + text: l10n.selectAndCrop, + close: false + }, + states: [ + new wp.media.controller.Library({ + title: l10n.chooseImage, + library: wp.media.query({ type: 'image' }), + multiple: false, + priority: 20, + suggestedWidth: _wpCustomizeHeader.data.width, + suggestedHeight: _wpCustomizeHeader.data.height + }), + new wp.media.controller.Cropper({ + imgSelectOptions: this.calculateImageSelectOptions + }) + ] + }); + + this.frame.on('select', this.onSelect, this); + this.frame.on('cropped', this.onCropped, this); + this.frame.on('skippedcrop', this.onSkippedCrop, this); + + this.frame.open(); + }, + + onSelect: function() { + this.frame.setState('cropper'); + }, + onCropped: function(croppedImage) { + var url = croppedImage.post_content, + attachmentId = croppedImage.attachment_id, + w = croppedImage.width, + h = croppedImage.height; + this.setImageFromURL(url, attachmentId, w, h); + }, + onSkippedCrop: function(selection) { + var url = selection.get('url'), + w = selection.get('width'), + h = selection.get('height'); + this.setImageFromURL(url, selection.id, w, h); + }, + + /** + * Creates a new wp.customize.HeaderTool.ImageModel from provided + * header image data and inserts it into the user-uploaded headers + * collection. + * + * @param {String} url + * @param {Number} attachmentId + * @param {Number} width + * @param {Number} height + */ + setImageFromURL: function(url, attachmentId, width, height) { + var choice, data = {}; + + data.url = url; + data.thumbnail_url = url; + data.timestamp = _.now(); + + if (attachmentId) { + data.attachment_id = attachmentId; + } + + if (width) { + data.width = width; + } + + if (height) { + data.height = height; + } + + choice = new api.HeaderTool.ImageModel({ + header: data, + choice: url.split('/').pop() + }); + api.HeaderTool.UploadsList.add(choice); + api.HeaderTool.currentHeader.set(choice.toJSON()); + choice.save(); + choice.importImage(); + }, + + /** + * Triggers the necessary events to deselect an image which was set as + * the currently selected one. + */ + removeImage: function() { + api.HeaderTool.currentHeader.trigger('hide'); + api.HeaderTool.CombinedList.trigger('control:removeImage'); + } + + }); + // Change objects contained within the main customize object to Settings. api.defaultConstructor = api.Setting; // Create the collection of Control objects. api.control = new api.Values({ defaultConstructor: api.Control }); + /** + * @constructor + * @augments wp.customize.Messenger + * @augments wp.customize.Class + * @mixes wp.customize.Events + */ api.PreviewFrame = api.Messenger.extend({ sensitivity: 2000, initialize: function( params, options ) { - var deferred = $.Deferred(), - self = this; + var deferred = $.Deferred(); // This is the promise object. deferred.promise( this ); @@ -356,6 +622,19 @@ this.bind( 'ready', this._ready ); + this.bind( 'ready', function ( data ) { + if ( ! data || ! data.activeControls ) { + return; + } + + $.each( data.activeControls, function ( id, active ) { + var control = api.control( id ); + if ( control ) { + control.active( active ); + } + } ); + } ); + this.request = $.ajax( this.previewUrl(), { type: 'POST', data: this.query, @@ -473,11 +752,22 @@ (function(){ var uuid = 0; + /** + * Create a universally unique identifier. + * + * @return {int} + */ api.PreviewFrame.uuid = function() { return 'preview-' + uuid++; }; }()); + /** + * @constructor + * @augments wp.customize.Messenger + * @augments wp.customize.Class + * @mixes wp.customize.Events + */ api.Previewer = api.Messenger.extend({ refreshBuffer: 250, @@ -488,8 +778,7 @@ */ initialize: function( params, options ) { var self = this, - rscheme = /^https?/, - url; + rscheme = /^https?/; $.extend( this, options || {} ); @@ -684,14 +973,11 @@ } }); - /* ===================================================================== - * Ready. - * ===================================================================== */ - api.controlConstructor = { color: api.ColorControl, upload: api.UploadControl, - image: api.ImageControl + image: api.ImageControl, + header: api.HeaderControl }; $( function() { @@ -706,21 +992,25 @@ if ( ! $.support.postMessage || ( ! $.support.cors && api.settings.isCrossDomain ) ) return window.location = api.settings.url.fallback; - var body = $( document.body ), - overlay = body.children('.wp-full-overlay'), - query, previewer, parent; + var parent, topFocus, + body = $( document.body ), + overlay = body.children( '.wp-full-overlay' ), + title = $( '#customize-info .theme-name.site-title' ), + closeBtn = $( '.customize-controls-close' ), + saveBtn = $( '#save' ); - // Prevent the form from saving when enter is pressed. + // Prevent the form from saving when enter is pressed on an input or select element. $('#customize-controls').on( 'keydown', function( e ) { - if ( $( e.target ).is('textarea') ) - return; + var isEnter = ( 13 === e.which ), + $el = $( e.target ); - if ( 13 === e.which ) // Enter + if ( isEnter && ( $el.is( 'input:not([type=button])' ) || $el.is( 'select' ) ) ) { e.preventDefault(); + } }); // Initialize Previewer - previewer = new api.Previewer({ + api.previewer = new api.Previewer({ container: '#customize-preview', form: '#customize-controls', previewUrl: api.settings.url.preview, @@ -733,9 +1023,9 @@ query: function() { return { wp_customize: 'on', - theme: api.settings.theme.stylesheet, - customized: JSON.stringify( api.get() ), - nonce: this.nonce.preview + theme: api.settings.theme.stylesheet, + customized: JSON.stringify( api.get() ), + nonce: this.nonce.preview }; }, @@ -744,48 +1034,67 @@ query = $.extend( this.query(), { action: 'customize_save', nonce: this.nonce.save - }), - request = $.post( api.settings.url.ajax, query ); - - api.trigger( 'save', request ); - - body.addClass('saving'); + } ), + processing = api.state( 'processing' ), + submitWhenDoneProcessing, + submit; + + body.addClass( 'saving' ); + + submit = function () { + var request = $.post( api.settings.url.ajax, query ); + + api.trigger( 'save', request ); + + request.always( function () { + body.removeClass( 'saving' ); + } ); + + request.done( function( response ) { + // Check if the user is logged out. + if ( '0' === response ) { + self.preview.iframe.hide(); + self.login().done( function() { + self.save(); + self.preview.iframe.show(); + } ); + return; + } - request.always( function() { - body.removeClass('saving'); - }); + // Check for cheaters. + if ( '-1' === response ) { + self.cheatin(); + return; + } - request.done( function( response ) { - // Check if the user is logged out. - if ( '0' === response ) { - self.preview.iframe.hide(); - self.login().done( function() { - self.save(); - self.preview.iframe.show(); - }); - return; - } + api.trigger( 'saved' ); + } ); + }; - // Check for cheaters. - if ( '-1' === response ) { - self.cheatin(); - return; - } + if ( 0 === processing() ) { + submit(); + } else { + submitWhenDoneProcessing = function () { + if ( 0 === processing() ) { + api.state.unbind( 'change', submitWhenDoneProcessing ); + submit(); + } + }; + api.state.bind( 'change', submitWhenDoneProcessing ); + } - api.trigger( 'saved' ); - }); } }); // Refresh the nonces if the preview sends updated nonces over. - previewer.bind( 'nonce', function( nonce ) { - $.extend( this.nonce, nonce ); - }); + api.previewer.bind( 'nonce', function( nonce ) { + $.extend( this.nonce, nonce ); + }); $.each( api.settings.settings, function( id, data ) { api.create( id, id, data.value, { transport: data.transport, - previewer: previewer + previewer: api.previewer } ); }); @@ -795,43 +1104,43 @@ control = api.control.add( id, new constructor( id, { params: data, - previewer: previewer + previewer: api.previewer } ) ); }); // Check if preview url is valid and load the preview frame. - if ( previewer.previewUrl() ) - previewer.refresh(); - else - previewer.previewUrl( api.settings.url.home ); + if ( api.previewer.previewUrl() ) { + api.previewer.refresh(); + } else { + api.previewer.previewUrl( api.settings.url.home ); + } // Save and activated states (function() { var state = new api.Values(), - saved = state.create('saved'), - activated = state.create('activated'); + saved = state.create( 'saved' ), + activated = state.create( 'activated' ), + processing = state.create( 'processing' ); state.bind( 'change', function() { - var save = $('#save'), - back = $('.back'); - if ( ! activated() ) { - save.val( api.l10n.activate ).prop( 'disabled', false ); - back.text( api.l10n.cancel ); + saveBtn.val( api.l10n.activate ).prop( 'disabled', false ); + closeBtn.find( '.screen-reader-text' ).text( api.l10n.cancel ); } else if ( saved() ) { - save.val( api.l10n.saved ).prop( 'disabled', true ); - back.text( api.l10n.close ); + saveBtn.val( api.l10n.saved ).prop( 'disabled', true ); + closeBtn.find( '.screen-reader-text' ).text( api.l10n.close ); } else { - save.val( api.l10n.save ).prop( 'disabled', false ); - back.text( api.l10n.cancel ); + saveBtn.val( api.l10n.save ).prop( 'disabled', false ); + closeBtn.find( '.screen-reader-text' ).text( api.l10n.cancel ); } }); // Set default states. saved( true ); activated( api.settings.theme.active ); + processing( 0 ); api.bind( 'change', function() { state('saved').set( false ); @@ -852,18 +1161,18 @@ }()); // Button bindings. - $('#save').click( function( event ) { - previewer.save(); + saveBtn.click( function( event ) { + api.previewer.save(); event.preventDefault(); }).keydown( function( event ) { if ( 9 === event.which ) // tab return; if ( 13 === event.which ) // enter - previewer.save(); + api.previewer.save(); event.preventDefault(); }); - $('.back').keydown( function( event ) { + closeBtn.keydown( function( event ) { if ( 9 === event.which ) // tab return; if ( 13 === event.which ) // enter @@ -884,6 +1193,13 @@ event.preventDefault(); }); + // Bind site title display to the corresponding field. + if ( title.length ) { + $( '#customize-control-blogname input' ).on( 'input', function() { + title.text( this.value ); + } ); + } + // Create a potential postMessage connection with the parent frame. parent = new api.Messenger({ url: api.settings.url.parent, @@ -893,16 +1209,25 @@ // If we receive a 'back' event, we're inside an iframe. // Send any clicks to the 'Return' link to the parent page. parent.bind( 'back', function() { - $('.back').on( 'click.back', function( event ) { + closeBtn.on( 'click.customize-controls-close', function( event ) { event.preventDefault(); parent.send( 'close' ); }); }); + // Prompt user with AYS dialog if leaving the Customizer with unsaved changes + $( window ).on( 'beforeunload', function () { + if ( ! api.state( 'saved' )() ) { + return api.l10n.saveAlert; + } + } ); + // Pass events through to the parent. - api.bind( 'saved', function() { - parent.send( 'saved' ); - }); + $.each( [ 'saved', 'change' ], function ( i, event ) { + api.bind( event, function() { + parent.send( event ); + }); + } ); // When activated, let the loader handle redirecting the page. // If no loader exists, redirect the page ourselves (if a url exists). @@ -920,15 +1245,15 @@ $.each({ 'background_image': { controls: [ 'background_repeat', 'background_position_x', 'background_attachment' ], - callback: function( to ) { return !! to } + callback: function( to ) { return !! to; } }, 'show_on_front': { controls: [ 'page_on_front', 'page_for_posts' ], - callback: function( to ) { return 'page' === to } + callback: function( to ) { return 'page' === to; } }, 'header_textcolor': { controls: [ 'header_textcolor' ], - callback: function( to ) { return 'blank' !== to } + callback: function( to ) { return 'blank' !== to; } } }, function( settingId, o ) { api( settingId, function( setting ) { @@ -966,39 +1291,10 @@ }); }); - // Handle header image data - api.control( 'header_image', function( control ) { - control.setting.bind( function( to ) { - if ( to === control.params.removed ) - control.settings.data.set( false ); - }); - - control.library.on( 'click', 'a', function( event ) { - control.settings.data.set( $(this).data('customizeHeaderImageData') ); - }); - - control.uploader.success = function( attachment ) { - var data; - - api.ImageControl.prototype.success.call( control, attachment ); - - data = { - attachment_id: attachment.get('id'), - url: attachment.get('url'), - thumbnail_url: attachment.get('url'), - height: attachment.get('height'), - width: attachment.get('width') - }; - - attachment.element.data( 'customizeHeaderImageData', data ); - control.settings.data.set( data ); - }; - }); - api.trigger( 'ready' ); // Make sure left column gets focus - var topFocus = $('.back'); + topFocus = closeBtn; topFocus.focus(); setTimeout(function () { topFocus.focus();