/* globals _wpCustomizeHeader, _wpMediaViewsL10n */ (function( exports, $ ){ var api = wp.customize; /** * @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 ) { api.Value.prototype.initialize.call( this, value, options ); this.id = id; this.transport = this.transport || 'refresh'; this.bind( this.preview ); }, preview: function() { switch ( this.transport ) { case 'refresh': return this.previewer.refresh(); case 'postMessage': return this.previewer.send( 'setting', [ this.id, this() ] ); } } }); api.Control = api.Class.extend({ initialize: function( id, options ) { var control = this, nodes, radios, settings; this.params = {}; $.extend( this, options || {} ); this.id = id; this.selector = '#customize-control-' + id.replace( /\]/g, '' ).replace( /\[/g, '-' ); this.container = $( this.selector ); settings = $.map( this.params.settings, function( value ) { return value; }); api.apply( api, settings.concat( function() { var key; control.settings = {}; for ( key in control.params.settings ) { control.settings[ key ] = api( control.params.settings[ key ] ); } control.setting = control.settings['default'] || null; control.ready(); }) ); control.elements = []; nodes = this.container.find('[data-customize-setting-link]'); radios = {}; nodes.each( function() { var node = $(this), name; if ( node.is(':radio') ) { name = node.prop('name'); if ( radios[ name ] ) return; radios[ name ] = true; node = nodes.filter( '[name="' + name + '"]' ); } api( node.data('customizeSettingLink'), function( setting ) { var element = new api.Element( node ); control.elements.push( element ); element.sync( setting ); element.set( setting() ); }); }); }, ready: function() {}, dropdownInit: function() { 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(); }; // 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 return; event.preventDefault(); if (!toggleFreeze) control.container.toggleClass('open'); if ( control.container.hasClass('open') ) control.container.parent().parent().find('li.library-selected').focus(); // Don't want to fire focus and click at same time toggleFreeze = true; setTimeout(function () { toggleFreeze = false; }, 400); }); this.setting.bind( update ); update( this.setting() ); } }); api.ColorControl = api.Control.extend({ ready: function() { var control = this, picker = this.container.find('.color-picker-hex'); picker.val( control.setting() ).wpColorPicker({ change: function() { control.setting.set( picker.wpColorPicker('color') ); }, clear: function() { control.setting.set( false ); } }); } }); api.UploadControl = api.Control.extend({ ready: function() { var control = this; this.params.removed = this.params.removed || ''; this.success = $.proxy( this.success, this ); this.uploader = $.extend({ container: this.container, browser: this.container.find('.upload'), dropzone: this.container.find('.upload-dropzone'), success: this.success, plupload: {}, params: {} }, this.uploader || {} ); if ( control.params.extensions ) { control.uploader.plupload.filters = [{ title: api.l10n.allowedFiles, extensions: control.params.extensions }]; } if ( control.params.context ) control.uploader.params['post_data[context]'] = this.params.context; if ( api.settings.theme.stylesheet ) control.uploader.params['post_data[theme]'] = api.settings.theme.stylesheet; this.uploader = new wp.Uploader( this.uploader ); this.remover = this.container.find('.remove'); this.remover.on( 'click keydown', function( event ) { if ( event.type === 'keydown' && 13 !== event.which ) // enter return; control.setting.set( control.params.removed ); event.preventDefault(); }); this.removerVisibility = $.proxy( this.removerVisibility, this ); this.setting.bind( this.removerVisibility ); this.removerVisibility( this.setting.get() ); }, success: function( attachment ) { this.setting.set( attachment.get('url') ); }, removerVisibility: function( to ) { this.remover.toggle( to != this.params.removed ); } }); api.ImageControl = api.UploadControl.extend({ ready: function() { var control = this, panels; this.uploader = { init: function() { var fallback, button; if ( this.supports.dragdrop ) return; // Maintain references while wrapping the fallback button. fallback = control.container.find( '.upload-fallback' ); button = fallback.children().detach(); this.browser.detach().empty().append( button ); fallback.append( this.browser ).show(); } }; api.UploadControl.prototype.ready.call( this ); this.thumbnail = this.container.find('.preview-thumbnail img'); this.thumbnailSrc = $.proxy( this.thumbnailSrc, this ); this.setting.bind( this.thumbnailSrc ); this.library = this.container.find('.library'); // Generate tab objects this.tabs = {}; panels = this.library.find('.library-content'); this.library.children('ul').children('li').each( function() { var link = $(this), id = link.data('customizeTab'), panel = panels.filter('[data-customize-tab="' + id + '"]'); control.tabs[ id ] = { both: link.add( panel ), link: link, panel: panel }; }); // Bind tab switch events this.library.children('ul').on( 'click keydown', 'li', function( event ) { if ( event.type === 'keydown' && 13 !== event.which ) // enter return; var id = $(this).data('customizeTab'), tab = control.tabs[ id ]; event.preventDefault(); if ( tab.link.hasClass('library-selected') ) return; control.selected.both.removeClass('library-selected'); control.selected = tab; control.selected.both.addClass('library-selected'); }); // Bind events to switch image urls. this.library.on( 'click keydown', 'a', function( event ) { if ( event.type === 'keydown' && 13 !== event.which ) // enter return; var value = $(this).data('customizeImageValue'); if ( value ) { control.setting.set( value ); event.preventDefault(); } }); if ( this.tabs.uploaded ) { this.tabs.uploaded.target = this.library.find('.uploaded-target'); if ( ! this.tabs.uploaded.panel.find('.thumbnail').length ) this.tabs.uploaded.both.addClass('hidden'); } // Select a tab panels.each( function() { var tab = control.tabs[ $(this).data('customizeTab') ]; // Select the first visible tab. if ( ! tab.link.hasClass('hidden') ) { control.selected = tab; tab.both.addClass('library-selected'); return false; } }); this.dropdownInit(); }, success: function( attachment ) { api.UploadControl.prototype.success.call( this, attachment ); // Add the uploaded image to the uploaded tab. if ( this.tabs.uploaded && this.tabs.uploaded.target.length ) { this.tabs.uploaded.both.removeClass('hidden'); // @todo: Do NOT store this on the attachment model. That is bad. attachment.element = $( '' ) .data( 'customizeImageValue', attachment.get('url') ) .append( '' ) .appendTo( this.tabs.uploaded.target ); } }, thumbnailSrc: function( to ) { if ( /^(https?:)?\/\//.test( to ) ) this.thumbnail.prop( 'src', to ).show(); else this.thumbnail.hide(); } }); api.HeaderControl = api.Control.extend({ ready: function() { this.btnRemove = $('.actions .remove'); this.btnNew = $('.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, parent: this.$el, 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 suggestedWidth, suggestedHeight, l10n = _wpMediaViewsL10n; event.preventDefault(); suggestedWidth = l10n.suggestedWidth.replace('%d', _wpCustomizeHeader.data.width); suggestedHeight = l10n.suggestedHeight.replace('%d', _wpCustomizeHeader.data.height); /* '' + suggestedWidth + ' ' + suggestedHeight + '' */ this.frame = wp.media({ title: l10n.chooseImage, library: { type: 'image' }, button: { text: l10n.selectAndCrop, close: false }, multiple: false, imgSelectOptions: this.calculateImageSelectOptions }); this.frame.states.add([new wp.media.controller.Cropper()]); 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; 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 }); api.PreviewFrame = api.Messenger.extend({ sensitivity: 2000, initialize: function( params, options ) { var deferred = $.Deferred(); // This is the promise object. deferred.promise( this ); this.container = params.container; this.signature = params.signature; $.extend( params, { channel: api.PreviewFrame.uuid() }); api.Messenger.prototype.initialize.call( this, params, options ); this.add( 'previewUrl', params.previewUrl ); this.query = $.extend( params.query || {}, { customize_messenger_channel: this.channel() }); this.run( deferred ); }, run: function( deferred ) { var self = this, loaded = false, ready = false; if ( this._ready ) this.unbind( 'ready', this._ready ); this._ready = function() { ready = true; if ( loaded ) deferred.resolveWith( self ); }; this.bind( 'ready', this._ready ); this.request = $.ajax( this.previewUrl(), { type: 'POST', data: this.query, xhrFields: { withCredentials: true } } ); this.request.fail( function() { deferred.rejectWith( self, [ 'request failure' ] ); }); this.request.done( function( response ) { var location = self.request.getResponseHeader('Location'), signature = self.signature, index; // Check if the location response header differs from the current URL. // If so, the request was redirected; try loading the requested page. if ( location && location != self.previewUrl() ) { deferred.rejectWith( self, [ 'redirect', location ] ); return; } // Check if the user is not logged in. if ( '0' === response ) { self.login( deferred ); return; } // Check for cheaters. if ( '-1' === response ) { deferred.rejectWith( self, [ 'cheatin' ] ); return; } // Check for a signature in the request. index = response.lastIndexOf( signature ); if ( -1 === index || index < response.lastIndexOf('') ) { deferred.rejectWith( self, [ 'unsigned' ] ); return; } // Strip the signature from the request. response = response.slice( 0, index ) + response.slice( index + signature.length ); // Create the iframe and inject the html content. self.iframe = $('