156
157
Container = api.Class.extend({
157
158
defaultActiveArguments: { duration: 'fast', completeCallback: $.noop },
158
159
defaultExpandedArguments: { duration: 'fast', completeCallback: $.noop },
160
containerType: 'container',
164
* @param {Object} options
174
* @param {string} id - The ID for the container.
175
* @param {object} options - Object containing one property: params.
176
* @param {object} options.params - Object containing the following properties.
177
* @param {string} options.params.title - Title shown when panel is collapsed and expanded.
178
* @param {string=} [options.params.description] - Description shown at the top of the panel.
179
* @param {number=100} [options.params.priority] - The sort priority for the panel.
180
* @param {string=default} [options.params.type] - The type of the panel. See wp.customize.panelConstructor.
181
* @param {string=} [options.params.content] - The markup to be used for the panel container. If empty, a JS template is used.
182
* @param {boolean=true} [options.params.active] - Whether the panel is active or not.
166
184
initialize: function ( id, options ) {
167
185
var container = this;
168
186
container.id = id;
169
container.params = {};
170
$.extend( container, options || {} );
187
options = options || {};
189
options.params = _.defaults(
190
options.params || {},
194
$.extend( container, options );
195
container.templateSelector = 'customize-' + container.containerType + '-' + container.params.type;
171
196
container.container = $( container.params.content );
197
if ( 0 === container.container.length ) {
198
container.container = $( container.getContainer() );
173
201
container.deferred = {
174
202
embedded: new $.Deferred()
501
583
onChangeExpanded: function ( expanded, args ) {
502
584
var section = this,
585
container = section.container.closest( '.wp-full-overlay-sidebar-content' ),
503
586
content = section.container.find( '.accordion-section-content' ),
587
overlay = section.container.closest( '.wp-full-overlay' ),
588
backBtn = section.container.find( '.customize-section-back' ),
589
sectionTitle = section.container.find( '.accordion-section-title' ).first(),
590
headerActionsHeight = $( '#customize-header-actions' ).height(),
591
resizeContentHeight, expand, position, scroll;
593
if ( expanded && ! section.container.hasClass( 'open' ) ) {
508
595
if ( args.unchanged ) {
509
596
expand = args.completeCallback;
511
expand = function () {
512
content.stop().slideDown( args.duration, args.completeCallback );
598
container.scrollTop( 0 );
599
resizeContentHeight = function() {
600
var matchMedia, offset;
601
matchMedia = window.matchMedia || window.msMatchMedia;
602
offset = 90; // 45px for customize header actions + 45px for footer actions.
604
// No footer on small screens.
605
if ( matchMedia && matchMedia( '(max-width: 640px)' ).matches ) {
608
content.css( 'height', ( window.innerHeight - offset ) );
610
expand = function() {
513
611
section.container.addClass( 'open' );
612
overlay.addClass( 'section-open' );
613
position = content.offset().top;
614
scroll = container.scrollTop();
615
content.css( 'margin-top', ( headerActionsHeight - position - scroll ) );
616
resizeContentHeight();
617
sectionTitle.attr( 'tabindex', '-1' );
618
backBtn.attr( 'tabindex', '0' );
620
if ( args.completeCallback ) {
621
args.completeCallback();
624
// Fix the height after browser resize.
625
$( window ).on( 'resize.customizer-section', _.debounce( resizeContentHeight, 100 ) );
627
// Fix the top margin after reflow.
628
api.bind( 'pane-contents-reflowed', _.debounce( function() {
629
var offset = ( content.offset().top - headerActionsHeight );
631
content.css( 'margin-top', ( parseInt( content.css( 'margin-top' ), 10 ) - offset ) );
1870
* A control for selecting and cropping an image.
1873
* @augments wp.customize.MediaControl
1874
* @augments wp.customize.Control
1875
* @augments wp.customize.Class
1877
api.CroppedImageControl = api.MediaControl.extend({
1880
* Open the media modal to the library state.
1882
openFrame: function( event ) {
1883
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
1888
this.frame.setState( 'library' ).open();
1892
* Create a media modal select frame, and store it so the instance can be reused when needed.
1894
initFrame: function() {
1895
var l10n = _wpMediaViewsL10n;
1897
this.frame = wp.media({
1903
new wp.media.controller.Library({
1904
title: this.params.button_labels.frame_title,
1905
library: wp.media.query({ type: 'image' }),
1909
suggestedWidth: this.params.width,
1910
suggestedHeight: this.params.height
1912
new wp.media.controller.CustomizeImageCropper({
1913
imgSelectOptions: this.calculateImageSelectOptions,
1919
this.frame.on( 'select', this.onSelect, this );
1920
this.frame.on( 'cropped', this.onCropped, this );
1921
this.frame.on( 'skippedcrop', this.onSkippedCrop, this );
1925
* After an image is selected in the media modal, switch to the cropper
1926
* state if the image isn't the right size.
1928
onSelect: function() {
1929
var attachment = this.frame.state().get( 'selection' ).first().toJSON();
1931
if ( this.params.width === attachment.width && this.params.height === attachment.height && ! this.params.flex_width && ! this.params.flex_height ) {
1932
this.setImageFromAttachment( attachment );
1935
this.frame.setState( 'cropper' );
1940
* After the image has been cropped, apply the cropped image data to the setting.
1942
* @param {object} croppedImage Cropped attachment data.
1944
onCropped: function( croppedImage ) {
1945
this.setImageFromAttachment( croppedImage );
1949
* Returns a set of options, computed from the attached image data and
1950
* control-specific data, to be fed to the imgAreaSelect plugin in
1951
* wp.media.view.Cropper.
1953
* @param {wp.media.model.Attachment} attachment
1954
* @param {wp.media.controller.Cropper} controller
1955
* @returns {Object} Options
1957
calculateImageSelectOptions: function( attachment, controller ) {
1958
var control = controller.get( 'control' ),
1959
flexWidth = !! parseInt( control.params.flex_width, 10 ),
1960
flexHeight = !! parseInt( control.params.flex_height, 10 ),
1961
realWidth = attachment.get( 'width' ),
1962
realHeight = attachment.get( 'height' ),
1963
xInit = parseInt( control.params.width, 10 ),
1964
yInit = parseInt( control.params.height, 10 ),
1965
ratio = xInit / yInit,
1968
x1, y1, imgSelectOptions;
1970
controller.set( 'canSkipCrop', ! control.mustBeCropped( flexWidth, flexHeight, xInit, yInit, realWidth, realHeight ) );
1972
if ( xImg / yImg > ratio ) {
1974
xInit = yInit * ratio;
1977
yInit = xInit / ratio;
1980
x1 = ( xImg - xInit ) / 2;
1981
y1 = ( yImg - yInit ) / 2;
1983
imgSelectOptions = {
1988
imageWidth: realWidth,
1989
imageHeight: realHeight,
1996
if ( flexHeight === false && flexWidth === false ) {
1997
imgSelectOptions.aspectRatio = xInit + ':' + yInit;
1999
if ( flexHeight === false ) {
2000
imgSelectOptions.maxHeight = yInit;
2002
if ( flexWidth === false ) {
2003
imgSelectOptions.maxWidth = xInit;
2006
return imgSelectOptions;
2010
* Return whether the image must be cropped, based on required dimensions.
2012
* @param {bool} flexW
2013
* @param {bool} flexH
2020
mustBeCropped: function( flexW, flexH, dstW, dstH, imgW, imgH ) {
2021
if ( true === flexW && true === flexH ) {
2025
if ( true === flexW && dstH === imgH ) {
2029
if ( true === flexH && dstW === imgW ) {
2033
if ( dstW === imgW && dstH === imgH ) {
2037
if ( imgW <= dstW ) {
2045
* If cropping was skipped, apply the image data directly to the setting.
2047
onSkippedCrop: function() {
2048
var attachment = this.frame.state().get( 'selection' ).first().toJSON();
2049
this.setImageFromAttachment( attachment );
2053
* Updates the setting and re-renders the control UI.
2055
* @param {object} attachment
2057
setImageFromAttachment: function( attachment ) {
2058
this.params.attachment = attachment;
2060
// Set the Customizer setting; the callback takes care of rendering.
2061
this.setting( attachment.id );
2066
* A control for selecting and cropping Site Icons.
2069
* @augments wp.customize.CroppedImageControl
2070
* @augments wp.customize.MediaControl
2071
* @augments wp.customize.Control
2072
* @augments wp.customize.Class
2074
api.SiteIconControl = api.CroppedImageControl.extend({
2077
* Create a media modal select frame, and store it so the instance can be reused when needed.
2079
initFrame: function() {
2080
var l10n = _wpMediaViewsL10n;
2082
this.frame = wp.media({
2088
new wp.media.controller.Library({
2089
title: this.params.button_labels.frame_title,
2090
library: wp.media.query({ type: 'image' }),
2094
suggestedWidth: this.params.width,
2095
suggestedHeight: this.params.height
2097
new wp.media.controller.SiteIconCropper({
2098
imgSelectOptions: this.calculateImageSelectOptions,
2104
this.frame.on( 'select', this.onSelect, this );
2105
this.frame.on( 'cropped', this.onCropped, this );
2106
this.frame.on( 'skippedcrop', this.onSkippedCrop, this );
2110
* After an image is selected in the media modal, switch to the cropper
2111
* state if the image isn't the right size.
2113
onSelect: function() {
2114
var attachment = this.frame.state().get( 'selection' ).first().toJSON(),
2117
if ( this.params.width === attachment.width && this.params.height === attachment.height && ! this.params.flex_width && ! this.params.flex_height ) {
2118
wp.ajax.post( 'crop-image', {
2119
nonce: attachment.nonces.edit,
2121
context: 'site-icon',
2125
width: this.params.width,
2126
height: this.params.height,
2127
dst_width: this.params.width,
2128
dst_height: this.params.height
2130
} ).done( function( croppedImage ) {
2131
controller.setImageFromAttachment( croppedImage );
2132
controller.frame.close();
2133
} ).fail( function() {
2134
controller.trigger('content:error:crop');
2137
this.frame.setState( 'cropper' );
2142
* Updates the setting and re-renders the control UI.
2144
* @param {object} attachment
2146
setImageFromAttachment: function( attachment ) {
2147
var icon = typeof attachment.sizes['site_icon-32'] !== 'undefined' ? attachment.sizes['site_icon-32'] : attachment.sizes.thumbnail;
2149
this.params.attachment = attachment;
2151
// Set the Customizer setting; the callback takes care of rendering.
2152
this.setting( attachment.id );
2155
// Update the icon in-browser.
2156
$( 'link[sizes="32x32"]' ).attr( 'href', icon.url );
2160
* Called when the "Remove" link is clicked. Empties the setting.
2162
* @param {object} event jQuery Event object
2164
removeFile: function( event ) {
2165
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
2168
event.preventDefault();
2170
this.params.attachment = {};
2172
this.renderContent(); // Not bound to setting change when emptying.
2173
$( 'link[rel="icon"]' ).attr( 'href', '' );
1689
2179
* @augments wp.customize.Control
1690
2180
* @augments wp.customize.Class