~canonical-sysadmins/wordpress/4.7.2

« back to all changes in this revision

Viewing changes to wp-admin/js/customize-controls.js

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* globals _wpCustomizeHeader, _wpMediaViewsL10n */
 
2
(function( exports, $ ){
 
3
        var api = wp.customize;
 
4
 
 
5
        /**
 
6
         * @constructor
 
7
         * @augments wp.customize.Value
 
8
         * @augments wp.customize.Class
 
9
         *
 
10
         * @param options
 
11
         * - previewer - The Previewer instance to sync with.
 
12
         * - transport - The transport to use for previewing. Supports 'refresh' and 'postMessage'.
 
13
         */
 
14
        api.Setting = api.Value.extend({
 
15
                initialize: function( id, value, options ) {
 
16
                        api.Value.prototype.initialize.call( this, value, options );
 
17
 
 
18
                        this.id = id;
 
19
                        this.transport = this.transport || 'refresh';
 
20
 
 
21
                        this.bind( this.preview );
 
22
                },
 
23
                preview: function() {
 
24
                        switch ( this.transport ) {
 
25
                                case 'refresh':
 
26
                                        return this.previewer.refresh();
 
27
                                case 'postMessage':
 
28
                                        return this.previewer.send( 'setting', [ this.id, this() ] );
 
29
                        }
 
30
                }
 
31
        });
 
32
 
 
33
        /**
 
34
         * @constructor
 
35
         * @augments wp.customize.Class
 
36
         */
 
37
        api.Control = api.Class.extend({
 
38
                initialize: function( id, options ) {
 
39
                        var control = this,
 
40
                                nodes, radios, settings;
 
41
 
 
42
                        this.params = {};
 
43
                        $.extend( this, options || {} );
 
44
 
 
45
                        this.id = id;
 
46
                        this.selector = '#customize-control-' + id.replace( /\]/g, '' ).replace( /\[/g, '-' );
 
47
                        this.container = $( this.selector );
 
48
                        this.active = new api.Value( this.params.active );
 
49
 
 
50
                        settings = $.map( this.params.settings, function( value ) {
 
51
                                return value;
 
52
                        });
 
53
 
 
54
                        api.apply( api, settings.concat( function() {
 
55
                                var key;
 
56
 
 
57
                                control.settings = {};
 
58
                                for ( key in control.params.settings ) {
 
59
                                        control.settings[ key ] = api( control.params.settings[ key ] );
 
60
                                }
 
61
 
 
62
                                control.setting = control.settings['default'] || null;
 
63
                                control.ready();
 
64
                        }) );
 
65
 
 
66
                        control.elements = [];
 
67
 
 
68
                        nodes  = this.container.find('[data-customize-setting-link]');
 
69
                        radios = {};
 
70
 
 
71
                        nodes.each( function() {
 
72
                                var node = $(this),
 
73
                                        name;
 
74
 
 
75
                                if ( node.is(':radio') ) {
 
76
                                        name = node.prop('name');
 
77
                                        if ( radios[ name ] )
 
78
                                                return;
 
79
 
 
80
                                        radios[ name ] = true;
 
81
                                        node = nodes.filter( '[name="' + name + '"]' );
 
82
                                }
 
83
 
 
84
                                api( node.data('customizeSettingLink'), function( setting ) {
 
85
                                        var element = new api.Element( node );
 
86
                                        control.elements.push( element );
 
87
                                        element.sync( setting );
 
88
                                        element.set( setting() );
 
89
                                });
 
90
                        });
 
91
 
 
92
                        control.active.bind( function ( active ) {
 
93
                                control.toggle( active );
 
94
                        } );
 
95
                        control.toggle( control.active() );
 
96
                },
 
97
 
 
98
                /**
 
99
                 * @abstract
 
100
                 */
 
101
                ready: function() {},
 
102
 
 
103
                /**
 
104
                 * Callback for change to the control's active state.
 
105
                 *
 
106
                 * Override function for custom behavior for the control being active/inactive.
 
107
                 *
 
108
                 * @param {Boolean} active
 
109
                 */
 
110
                toggle: function ( active ) {
 
111
                        if ( active ) {
 
112
                                this.container.slideDown();
 
113
                        } else {
 
114
                                this.container.slideUp();
 
115
                        }
 
116
                },
 
117
 
 
118
                dropdownInit: function() {
 
119
                        var control      = this,
 
120
                                statuses     = this.container.find('.dropdown-status'),
 
121
                                params       = this.params,
 
122
                                toggleFreeze = false,
 
123
                                update       = function( to ) {
 
124
                                        if ( typeof to === 'string' && params.statuses && params.statuses[ to ] )
 
125
                                                statuses.html( params.statuses[ to ] ).show();
 
126
                                        else
 
127
                                                statuses.hide();
 
128
                                };
 
129
 
 
130
                        // Support the .dropdown class to open/close complex elements
 
131
                        this.container.on( 'click keydown', '.dropdown', function( event ) {
 
132
                                if ( event.type === 'keydown' &&  13 !== event.which ) // enter
 
133
                                        return;
 
134
 
 
135
                                event.preventDefault();
 
136
 
 
137
                                if (!toggleFreeze)
 
138
                                        control.container.toggleClass('open');
 
139
 
 
140
                                if ( control.container.hasClass('open') )
 
141
                                        control.container.parent().parent().find('li.library-selected').focus();
 
142
 
 
143
                                // Don't want to fire focus and click at same time
 
144
                                toggleFreeze = true;
 
145
                                setTimeout(function () {
 
146
                                        toggleFreeze = false;
 
147
                                }, 400);
 
148
                        });
 
149
 
 
150
                        this.setting.bind( update );
 
151
                        update( this.setting() );
 
152
                }
 
153
        });
 
154
 
 
155
        /**
 
156
         * @constructor
 
157
         * @augments wp.customize.Control
 
158
         * @augments wp.customize.Class
 
159
         */
 
160
        api.ColorControl = api.Control.extend({
 
161
                ready: function() {
 
162
                        var control = this,
 
163
                                picker = this.container.find('.color-picker-hex');
 
164
 
 
165
                        picker.val( control.setting() ).wpColorPicker({
 
166
                                change: function() {
 
167
                                        control.setting.set( picker.wpColorPicker('color') );
 
168
                                },
 
169
                                clear: function() {
 
170
                                        control.setting.set( false );
 
171
                                }
 
172
                        });
 
173
                }
 
174
        });
 
175
 
 
176
        /**
 
177
         * @constructor
 
178
         * @augments wp.customize.Control
 
179
         * @augments wp.customize.Class
 
180
         */
 
181
        api.UploadControl = api.Control.extend({
 
182
                ready: function() {
 
183
                        var control = this;
 
184
 
 
185
                        this.params.removed = this.params.removed || '';
 
186
 
 
187
                        this.success = $.proxy( this.success, this );
 
188
 
 
189
                        this.uploader = $.extend({
 
190
                                container: this.container,
 
191
                                browser:   this.container.find('.upload'),
 
192
                                dropzone:  this.container.find('.upload-dropzone'),
 
193
                                success:   this.success,
 
194
                                plupload:  {},
 
195
                                params:    {}
 
196
                        }, this.uploader || {} );
 
197
 
 
198
                        if ( control.params.extensions ) {
 
199
                                control.uploader.plupload.filters = [{
 
200
                                        title:      api.l10n.allowedFiles,
 
201
                                        extensions: control.params.extensions
 
202
                                }];
 
203
                        }
 
204
 
 
205
                        if ( control.params.context )
 
206
                                control.uploader.params['post_data[context]'] = this.params.context;
 
207
 
 
208
                        if ( api.settings.theme.stylesheet )
 
209
                                control.uploader.params['post_data[theme]'] = api.settings.theme.stylesheet;
 
210
 
 
211
                        this.uploader = new wp.Uploader( this.uploader );
 
212
 
 
213
                        this.remover = this.container.find('.remove');
 
214
                        this.remover.on( 'click keydown', function( event ) {
 
215
                                if ( event.type === 'keydown' &&  13 !== event.which ) // enter
 
216
                                        return;
 
217
 
 
218
                                control.setting.set( control.params.removed );
 
219
                                event.preventDefault();
 
220
                        });
 
221
 
 
222
                        this.removerVisibility = $.proxy( this.removerVisibility, this );
 
223
                        this.setting.bind( this.removerVisibility );
 
224
                        this.removerVisibility( this.setting.get() );
 
225
                },
 
226
                success: function( attachment ) {
 
227
                        this.setting.set( attachment.get('url') );
 
228
                },
 
229
                removerVisibility: function( to ) {
 
230
                        this.remover.toggle( to != this.params.removed );
 
231
                }
 
232
        });
 
233
 
 
234
        /**
 
235
         * @constructor
 
236
         * @augments wp.customize.UploadControl
 
237
         * @augments wp.customize.Control
 
238
         * @augments wp.customize.Class
 
239
         */
 
240
        api.ImageControl = api.UploadControl.extend({
 
241
                ready: function() {
 
242
                        var control = this,
 
243
                                panels;
 
244
 
 
245
                        this.uploader = {
 
246
                                init: function() {
 
247
                                        var fallback, button;
 
248
 
 
249
                                        if ( this.supports.dragdrop )
 
250
                                                return;
 
251
 
 
252
                                        // Maintain references while wrapping the fallback button.
 
253
                                        fallback = control.container.find( '.upload-fallback' );
 
254
                                        button   = fallback.children().detach();
 
255
 
 
256
                                        this.browser.detach().empty().append( button );
 
257
                                        fallback.append( this.browser ).show();
 
258
                                }
 
259
                        };
 
260
 
 
261
                        api.UploadControl.prototype.ready.call( this );
 
262
 
 
263
                        this.thumbnail    = this.container.find('.preview-thumbnail img');
 
264
                        this.thumbnailSrc = $.proxy( this.thumbnailSrc, this );
 
265
                        this.setting.bind( this.thumbnailSrc );
 
266
 
 
267
                        this.library = this.container.find('.library');
 
268
 
 
269
                        // Generate tab objects
 
270
                        this.tabs = {};
 
271
                        panels    = this.library.find('.library-content');
 
272
 
 
273
                        this.library.children('ul').children('li').each( function() {
 
274
                                var link  = $(this),
 
275
                                        id    = link.data('customizeTab'),
 
276
                                        panel = panels.filter('[data-customize-tab="' + id + '"]');
 
277
 
 
278
                                control.tabs[ id ] = {
 
279
                                        both:  link.add( panel ),
 
280
                                        link:  link,
 
281
                                        panel: panel
 
282
                                };
 
283
                        });
 
284
 
 
285
                        // Bind tab switch events
 
286
                        this.library.children('ul').on( 'click keydown', 'li', function( event ) {
 
287
                                if ( event.type === 'keydown' &&  13 !== event.which ) // enter
 
288
                                        return;
 
289
 
 
290
                                var id  = $(this).data('customizeTab'),
 
291
                                        tab = control.tabs[ id ];
 
292
 
 
293
                                event.preventDefault();
 
294
 
 
295
                                if ( tab.link.hasClass('library-selected') )
 
296
                                        return;
 
297
 
 
298
                                control.selected.both.removeClass('library-selected');
 
299
                                control.selected = tab;
 
300
                                control.selected.both.addClass('library-selected');
 
301
                        });
 
302
 
 
303
                        // Bind events to switch image urls.
 
304
                        this.library.on( 'click keydown', 'a', function( event ) {
 
305
                                if ( event.type === 'keydown' && 13 !== event.which ) // enter
 
306
                                        return;
 
307
 
 
308
                                var value = $(this).data('customizeImageValue');
 
309
 
 
310
                                if ( value ) {
 
311
                                        control.setting.set( value );
 
312
                                        event.preventDefault();
 
313
                                }
 
314
                        });
 
315
 
 
316
                        if ( this.tabs.uploaded ) {
 
317
                                this.tabs.uploaded.target = this.library.find('.uploaded-target');
 
318
                                if ( ! this.tabs.uploaded.panel.find('.thumbnail').length )
 
319
                                        this.tabs.uploaded.both.addClass('hidden');
 
320
                        }
 
321
 
 
322
                        // Select a tab
 
323
                        panels.each( function() {
 
324
                                var tab = control.tabs[ $(this).data('customizeTab') ];
 
325
 
 
326
                                // Select the first visible tab.
 
327
                                if ( ! tab.link.hasClass('hidden') ) {
 
328
                                        control.selected = tab;
 
329
                                        tab.both.addClass('library-selected');
 
330
                                        return false;
 
331
                                }
 
332
                        });
 
333
 
 
334
                        this.dropdownInit();
 
335
                },
 
336
                success: function( attachment ) {
 
337
                        api.UploadControl.prototype.success.call( this, attachment );
 
338
 
 
339
                        // Add the uploaded image to the uploaded tab.
 
340
                        if ( this.tabs.uploaded && this.tabs.uploaded.target.length ) {
 
341
                                this.tabs.uploaded.both.removeClass('hidden');
 
342
 
 
343
                                // @todo: Do NOT store this on the attachment model. That is bad.
 
344
                                attachment.element = $( '<a href="#" class="thumbnail"></a>' )
 
345
                                        .data( 'customizeImageValue', attachment.get('url') )
 
346
                                        .append( '<img src="' +  attachment.get('url')+ '" />' )
 
347
                                        .appendTo( this.tabs.uploaded.target );
 
348
                        }
 
349
                },
 
350
                thumbnailSrc: function( to ) {
 
351
                        if ( /^(https?:)?\/\//.test( to ) )
 
352
                                this.thumbnail.prop( 'src', to ).show();
 
353
                        else
 
354
                                this.thumbnail.hide();
 
355
                }
 
356
        });
 
357
 
 
358
        /**
 
359
         * @constructor
 
360
         * @augments wp.customize.Control
 
361
         * @augments wp.customize.Class
 
362
         */
 
363
        api.HeaderControl = api.Control.extend({
 
364
                ready: function() {
 
365
                        this.btnRemove        = $('#customize-control-header_image .actions .remove');
 
366
                        this.btnNew           = $('#customize-control-header_image .actions .new');
 
367
 
 
368
                        _.bindAll(this, 'openMedia', 'removeImage');
 
369
 
 
370
                        this.btnNew.on( 'click', this.openMedia );
 
371
                        this.btnRemove.on( 'click', this.removeImage );
 
372
 
 
373
                        api.HeaderTool.currentHeader = new api.HeaderTool.ImageModel();
 
374
 
 
375
                        new api.HeaderTool.CurrentView({
 
376
                                model: api.HeaderTool.currentHeader,
 
377
                                el: '.current .container'
 
378
                        });
 
379
 
 
380
                        new api.HeaderTool.ChoiceListView({
 
381
                                collection: api.HeaderTool.UploadsList = new api.HeaderTool.ChoiceList(),
 
382
                                el: '.choices .uploaded .list'
 
383
                        });
 
384
 
 
385
                        new api.HeaderTool.ChoiceListView({
 
386
                                collection: api.HeaderTool.DefaultsList = new api.HeaderTool.DefaultsList(),
 
387
                                el: '.choices .default .list'
 
388
                        });
 
389
 
 
390
                        api.HeaderTool.combinedList = api.HeaderTool.CombinedList = new api.HeaderTool.CombinedList([
 
391
                                api.HeaderTool.UploadsList,
 
392
                                api.HeaderTool.DefaultsList
 
393
                        ]);
 
394
                },
 
395
 
 
396
                /**
 
397
                 * Returns a set of options, computed from the attached image data and
 
398
                 * theme-specific data, to be fed to the imgAreaSelect plugin in
 
399
                 * wp.media.view.Cropper.
 
400
                 *
 
401
                 * @param {wp.media.model.Attachment} attachment
 
402
                 * @param {wp.media.controller.Cropper} controller
 
403
                 * @returns {Object} Options
 
404
                 */
 
405
                calculateImageSelectOptions: function(attachment, controller) {
 
406
                        var xInit = parseInt(_wpCustomizeHeader.data.width, 10),
 
407
                                yInit = parseInt(_wpCustomizeHeader.data.height, 10),
 
408
                                flexWidth = !! parseInt(_wpCustomizeHeader.data['flex-width'], 10),
 
409
                                flexHeight = !! parseInt(_wpCustomizeHeader.data['flex-height'], 10),
 
410
                                ratio, xImg, yImg, realHeight, realWidth,
 
411
                                imgSelectOptions;
 
412
 
 
413
                        realWidth = attachment.get('width');
 
414
                        realHeight = attachment.get('height');
 
415
 
 
416
                        this.headerImage = new api.HeaderTool.ImageModel();
 
417
                        this.headerImage.set({
 
418
                                themeWidth: xInit,
 
419
                                themeHeight: yInit,
 
420
                                themeFlexWidth: flexWidth,
 
421
                                themeFlexHeight: flexHeight,
 
422
                                imageWidth: realWidth,
 
423
                                imageHeight: realHeight
 
424
                        });
 
425
 
 
426
                        controller.set( 'canSkipCrop', ! this.headerImage.shouldBeCropped() );
 
427
 
 
428
                        ratio = xInit / yInit;
 
429
                        xImg = realWidth;
 
430
                        yImg = realHeight;
 
431
 
 
432
                        if ( xImg / yImg > ratio ) {
 
433
                                yInit = yImg;
 
434
                                xInit = yInit * ratio;
 
435
                        } else {
 
436
                                xInit = xImg;
 
437
                                yInit = xInit / ratio;
 
438
                        }
 
439
 
 
440
                        imgSelectOptions = {
 
441
                                handles: true,
 
442
                                keys: true,
 
443
                                instance: true,
 
444
                                persistent: true,
 
445
                                imageWidth: realWidth,
 
446
                                imageHeight: realHeight,
 
447
                                x1: 0,
 
448
                                y1: 0,
 
449
                                x2: xInit,
 
450
                                y2: yInit
 
451
                        };
 
452
 
 
453
                        if (flexHeight === false && flexWidth === false) {
 
454
                                imgSelectOptions.aspectRatio = xInit + ':' + yInit;
 
455
                        }
 
456
                        if (flexHeight === false ) {
 
457
                                imgSelectOptions.maxHeight = yInit;
 
458
                        }
 
459
                        if (flexWidth === false ) {
 
460
                                imgSelectOptions.maxWidth = xInit;
 
461
                        }
 
462
 
 
463
                        return imgSelectOptions;
 
464
                },
 
465
 
 
466
                /**
 
467
                 * Sets up and opens the Media Manager in order to select an image.
 
468
                 * Depending on both the size of the image and the properties of the
 
469
                 * current theme, a cropping step after selection may be required or
 
470
                 * skippable.
 
471
                 *
 
472
                 * @param {event} event
 
473
                 */
 
474
                openMedia: function(event) {
 
475
                        var l10n = _wpMediaViewsL10n;
 
476
 
 
477
                        event.preventDefault();
 
478
 
 
479
                        this.frame = wp.media({
 
480
                                button: {
 
481
                                        text: l10n.selectAndCrop,
 
482
                                        close: false
 
483
                                },
 
484
                                states: [
 
485
                                        new wp.media.controller.Library({
 
486
                                                title:     l10n.chooseImage,
 
487
                                                library:   wp.media.query({ type: 'image' }),
 
488
                                                multiple:  false,
 
489
                                                priority:  20,
 
490
                                                suggestedWidth: _wpCustomizeHeader.data.width,
 
491
                                                suggestedHeight: _wpCustomizeHeader.data.height
 
492
                                        }),
 
493
                                        new wp.media.controller.Cropper({
 
494
                                                imgSelectOptions: this.calculateImageSelectOptions
 
495
                                        })
 
496
                                ]
 
497
                        });
 
498
 
 
499
                        this.frame.on('select', this.onSelect, this);
 
500
                        this.frame.on('cropped', this.onCropped, this);
 
501
                        this.frame.on('skippedcrop', this.onSkippedCrop, this);
 
502
 
 
503
                        this.frame.open();
 
504
                },
 
505
 
 
506
                onSelect: function() {
 
507
                        this.frame.setState('cropper');
 
508
                },
 
509
                onCropped: function(croppedImage) {
 
510
                        var url = croppedImage.post_content,
 
511
                                attachmentId = croppedImage.attachment_id,
 
512
                                w = croppedImage.width,
 
513
                                h = croppedImage.height;
 
514
                        this.setImageFromURL(url, attachmentId, w, h);
 
515
                },
 
516
                onSkippedCrop: function(selection) {
 
517
                        var url = selection.get('url'),
 
518
                                w = selection.get('width'),
 
519
                                h = selection.get('height');
 
520
                        this.setImageFromURL(url, selection.id, w, h);
 
521
                },
 
522
 
 
523
                /**
 
524
                 * Creates a new wp.customize.HeaderTool.ImageModel from provided
 
525
                 * header image data and inserts it into the user-uploaded headers
 
526
                 * collection.
 
527
                 *
 
528
                 * @param {String} url
 
529
                 * @param {Number} attachmentId
 
530
                 * @param {Number} width
 
531
                 * @param {Number} height
 
532
                 */
 
533
                setImageFromURL: function(url, attachmentId, width, height) {
 
534
                        var choice, data = {};
 
535
 
 
536
                        data.url = url;
 
537
                        data.thumbnail_url = url;
 
538
                        data.timestamp = _.now();
 
539
 
 
540
                        if (attachmentId) {
 
541
                                data.attachment_id = attachmentId;
 
542
                        }
 
543
 
 
544
                        if (width) {
 
545
                                data.width = width;
 
546
                        }
 
547
 
 
548
                        if (height) {
 
549
                                data.height = height;
 
550
                        }
 
551
 
 
552
                        choice = new api.HeaderTool.ImageModel({
 
553
                                header: data,
 
554
                                choice: url.split('/').pop()
 
555
                        });
 
556
                        api.HeaderTool.UploadsList.add(choice);
 
557
                        api.HeaderTool.currentHeader.set(choice.toJSON());
 
558
                        choice.save();
 
559
                        choice.importImage();
 
560
                },
 
561
 
 
562
                /**
 
563
                 * Triggers the necessary events to deselect an image which was set as
 
564
                 * the currently selected one.
 
565
                 */
 
566
                removeImage: function() {
 
567
                        api.HeaderTool.currentHeader.trigger('hide');
 
568
                        api.HeaderTool.CombinedList.trigger('control:removeImage');
 
569
                }
 
570
 
 
571
        });
 
572
 
 
573
        // Change objects contained within the main customize object to Settings.
 
574
        api.defaultConstructor = api.Setting;
 
575
 
 
576
        // Create the collection of Control objects.
 
577
        api.control = new api.Values({ defaultConstructor: api.Control });
 
578
 
 
579
        /**
 
580
         * @constructor
 
581
         * @augments wp.customize.Messenger
 
582
         * @augments wp.customize.Class
 
583
         * @mixes wp.customize.Events
 
584
         */
 
585
        api.PreviewFrame = api.Messenger.extend({
 
586
                sensitivity: 2000,
 
587
 
 
588
                initialize: function( params, options ) {
 
589
                        var deferred = $.Deferred();
 
590
 
 
591
                        // This is the promise object.
 
592
                        deferred.promise( this );
 
593
 
 
594
                        this.container = params.container;
 
595
                        this.signature = params.signature;
 
596
 
 
597
                        $.extend( params, { channel: api.PreviewFrame.uuid() });
 
598
 
 
599
                        api.Messenger.prototype.initialize.call( this, params, options );
 
600
 
 
601
                        this.add( 'previewUrl', params.previewUrl );
 
602
 
 
603
                        this.query = $.extend( params.query || {}, { customize_messenger_channel: this.channel() });
 
604
 
 
605
                        this.run( deferred );
 
606
                },
 
607
 
 
608
                run: function( deferred ) {
 
609
                        var self   = this,
 
610
                                loaded = false,
 
611
                                ready  = false;
 
612
 
 
613
                        if ( this._ready )
 
614
                                this.unbind( 'ready', this._ready );
 
615
 
 
616
                        this._ready = function() {
 
617
                                ready = true;
 
618
 
 
619
                                if ( loaded )
 
620
                                        deferred.resolveWith( self );
 
621
                        };
 
622
 
 
623
                        this.bind( 'ready', this._ready );
 
624
 
 
625
                        this.bind( 'ready', function ( data ) {
 
626
                                if ( ! data || ! data.activeControls ) {
 
627
                                        return;
 
628
                                }
 
629
 
 
630
                                $.each( data.activeControls, function ( id, active ) {
 
631
                                        var control = api.control( id );
 
632
                                        if ( control ) {
 
633
                                                control.active( active );
 
634
                                        }
 
635
                                } );
 
636
                        } );
 
637
 
 
638
                        this.request = $.ajax( this.previewUrl(), {
 
639
                                type: 'POST',
 
640
                                data: this.query,
 
641
                                xhrFields: {
 
642
                                        withCredentials: true
 
643
                                }
 
644
                        } );
 
645
 
 
646
                        this.request.fail( function() {
 
647
                                deferred.rejectWith( self, [ 'request failure' ] );
 
648
                        });
 
649
 
 
650
                        this.request.done( function( response ) {
 
651
                                var location = self.request.getResponseHeader('Location'),
 
652
                                        signature = self.signature,
 
653
                                        index;
 
654
 
 
655
                                // Check if the location response header differs from the current URL.
 
656
                                // If so, the request was redirected; try loading the requested page.
 
657
                                if ( location && location != self.previewUrl() ) {
 
658
                                        deferred.rejectWith( self, [ 'redirect', location ] );
 
659
                                        return;
 
660
                                }
 
661
 
 
662
                                // Check if the user is not logged in.
 
663
                                if ( '0' === response ) {
 
664
                                        self.login( deferred );
 
665
                                        return;
 
666
                                }
 
667
 
 
668
                                // Check for cheaters.
 
669
                                if ( '-1' === response ) {
 
670
                                        deferred.rejectWith( self, [ 'cheatin' ] );
 
671
                                        return;
 
672
                                }
 
673
 
 
674
                                // Check for a signature in the request.
 
675
                                index = response.lastIndexOf( signature );
 
676
                                if ( -1 === index || index < response.lastIndexOf('</html>') ) {
 
677
                                        deferred.rejectWith( self, [ 'unsigned' ] );
 
678
                                        return;
 
679
                                }
 
680
 
 
681
                                // Strip the signature from the request.
 
682
                                response = response.slice( 0, index ) + response.slice( index + signature.length );
 
683
 
 
684
                                // Create the iframe and inject the html content.
 
685
                                self.iframe = $('<iframe />').appendTo( self.container );
 
686
 
 
687
                                // Bind load event after the iframe has been added to the page;
 
688
                                // otherwise it will fire when injected into the DOM.
 
689
                                self.iframe.one( 'load', function() {
 
690
                                        loaded = true;
 
691
 
 
692
                                        if ( ready ) {
 
693
                                                deferred.resolveWith( self );
 
694
                                        } else {
 
695
                                                setTimeout( function() {
 
696
                                                        deferred.rejectWith( self, [ 'ready timeout' ] );
 
697
                                                }, self.sensitivity );
 
698
                                        }
 
699
                                });
 
700
 
 
701
                                self.targetWindow( self.iframe[0].contentWindow );
 
702
 
 
703
                                self.targetWindow().document.open();
 
704
                                self.targetWindow().document.write( response );
 
705
                                self.targetWindow().document.close();
 
706
                        });
 
707
                },
 
708
 
 
709
                login: function( deferred ) {
 
710
                        var self = this,
 
711
                                reject;
 
712
 
 
713
                        reject = function() {
 
714
                                deferred.rejectWith( self, [ 'logged out' ] );
 
715
                        };
 
716
 
 
717
                        if ( this.triedLogin )
 
718
                                return reject();
 
719
 
 
720
                        // Check if we have an admin cookie.
 
721
                        $.get( api.settings.url.ajax, {
 
722
                                action: 'logged-in'
 
723
                        }).fail( reject ).done( function( response ) {
 
724
                                var iframe;
 
725
 
 
726
                                if ( '1' !== response )
 
727
                                        reject();
 
728
 
 
729
                                iframe = $('<iframe src="' + self.previewUrl() + '" />').hide();
 
730
                                iframe.appendTo( self.container );
 
731
                                iframe.load( function() {
 
732
                                        self.triedLogin = true;
 
733
 
 
734
                                        iframe.remove();
 
735
                                        self.run( deferred );
 
736
                                });
 
737
                        });
 
738
                },
 
739
 
 
740
                destroy: function() {
 
741
                        api.Messenger.prototype.destroy.call( this );
 
742
                        this.request.abort();
 
743
 
 
744
                        if ( this.iframe )
 
745
                                this.iframe.remove();
 
746
 
 
747
                        delete this.request;
 
748
                        delete this.iframe;
 
749
                        delete this.targetWindow;
 
750
                }
 
751
        });
 
752
 
 
753
        (function(){
 
754
                var uuid = 0;
 
755
                /**
 
756
                 * Create a universally unique identifier.
 
757
                 *
 
758
                 * @return {int}
 
759
                 */
 
760
                api.PreviewFrame.uuid = function() {
 
761
                        return 'preview-' + uuid++;
 
762
                };
 
763
        }());
 
764
 
 
765
        /**
 
766
         * @constructor
 
767
         * @augments wp.customize.Messenger
 
768
         * @augments wp.customize.Class
 
769
         * @mixes wp.customize.Events
 
770
         */
 
771
        api.Previewer = api.Messenger.extend({
 
772
                refreshBuffer: 250,
 
773
 
 
774
                /**
 
775
                 * Requires params:
 
776
                 *  - container  - a selector or jQuery element
 
777
                 *  - previewUrl - the URL of preview frame
 
778
                 */
 
779
                initialize: function( params, options ) {
 
780
                        var self = this,
 
781
                                rscheme = /^https?/;
 
782
 
 
783
                        $.extend( this, options || {} );
 
784
 
 
785
                        /*
 
786
                         * Wrap this.refresh to prevent it from hammering the servers:
 
787
                         *
 
788
                         * If refresh is called once and no other refresh requests are
 
789
                         * loading, trigger the request immediately.
 
790
                         *
 
791
                         * If refresh is called while another refresh request is loading,
 
792
                         * debounce the refresh requests:
 
793
                         * 1. Stop the loading request (as it is instantly outdated).
 
794
                         * 2. Trigger the new request once refresh hasn't been called for
 
795
                         *    self.refreshBuffer milliseconds.
 
796
                         */
 
797
                        this.refresh = (function( self ) {
 
798
                                var refresh  = self.refresh,
 
799
                                        callback = function() {
 
800
                                                timeout = null;
 
801
                                                refresh.call( self );
 
802
                                        },
 
803
                                        timeout;
 
804
 
 
805
                                return function() {
 
806
                                        if ( typeof timeout !== 'number' ) {
 
807
                                                if ( self.loading ) {
 
808
                                                        self.abort();
 
809
                                                } else {
 
810
                                                        return callback();
 
811
                                                }
 
812
                                        }
 
813
 
 
814
                                        clearTimeout( timeout );
 
815
                                        timeout = setTimeout( callback, self.refreshBuffer );
 
816
                                };
 
817
                        })( this );
 
818
 
 
819
                        this.container   = api.ensure( params.container );
 
820
                        this.allowedUrls = params.allowedUrls;
 
821
                        this.signature   = params.signature;
 
822
 
 
823
                        params.url = window.location.href;
 
824
 
 
825
                        api.Messenger.prototype.initialize.call( this, params );
 
826
 
 
827
                        this.add( 'scheme', this.origin() ).link( this.origin ).setter( function( to ) {
 
828
                                var match = to.match( rscheme );
 
829
                                return match ? match[0] : '';
 
830
                        });
 
831
 
 
832
                        // Limit the URL to internal, front-end links.
 
833
                        //
 
834
                        // If the frontend and the admin are served from the same domain, load the
 
835
                        // preview over ssl if the customizer is being loaded over ssl. This avoids
 
836
                        // insecure content warnings. This is not attempted if the admin and frontend
 
837
                        // are on different domains to avoid the case where the frontend doesn't have
 
838
                        // ssl certs.
 
839
 
 
840
                        this.add( 'previewUrl', params.previewUrl ).setter( function( to ) {
 
841
                                var result;
 
842
 
 
843
                                // Check for URLs that include "/wp-admin/" or end in "/wp-admin".
 
844
                                // Strip hashes and query strings before testing.
 
845
                                if ( /\/wp-admin(\/|$)/.test( to.replace( /[#?].*$/, '' ) ) )
 
846
                                        return null;
 
847
 
 
848
                                // Attempt to match the URL to the control frame's scheme
 
849
                                // and check if it's allowed. If not, try the original URL.
 
850
                                $.each([ to.replace( rscheme, self.scheme() ), to ], function( i, url ) {
 
851
                                        $.each( self.allowedUrls, function( i, allowed ) {
 
852
                                                var path;
 
853
 
 
854
                                                allowed = allowed.replace( /\/+$/, '' );
 
855
                                                path = url.replace( allowed, '' );
 
856
 
 
857
                                                if ( 0 === url.indexOf( allowed ) && /^([/#?]|$)/.test( path ) ) {
 
858
                                                        result = url;
 
859
                                                        return false;
 
860
                                                }
 
861
                                        });
 
862
                                        if ( result )
 
863
                                                return false;
 
864
                                });
 
865
 
 
866
                                // If we found a matching result, return it. If not, bail.
 
867
                                return result ? result : null;
 
868
                        });
 
869
 
 
870
                        // Refresh the preview when the URL is changed (but not yet).
 
871
                        this.previewUrl.bind( this.refresh );
 
872
 
 
873
                        this.scroll = 0;
 
874
                        this.bind( 'scroll', function( distance ) {
 
875
                                this.scroll = distance;
 
876
                        });
 
877
 
 
878
                        // Update the URL when the iframe sends a URL message.
 
879
                        this.bind( 'url', this.previewUrl );
 
880
                },
 
881
 
 
882
                query: function() {},
 
883
 
 
884
                abort: function() {
 
885
                        if ( this.loading ) {
 
886
                                this.loading.destroy();
 
887
                                delete this.loading;
 
888
                        }
 
889
                },
 
890
 
 
891
                refresh: function() {
 
892
                        var self = this;
 
893
 
 
894
                        this.abort();
 
895
 
 
896
                        this.loading = new api.PreviewFrame({
 
897
                                url:        this.url(),
 
898
                                previewUrl: this.previewUrl(),
 
899
                                query:      this.query() || {},
 
900
                                container:  this.container,
 
901
                                signature:  this.signature
 
902
                        });
 
903
 
 
904
                        this.loading.done( function() {
 
905
                                // 'this' is the loading frame
 
906
                                this.bind( 'synced', function() {
 
907
                                        if ( self.preview )
 
908
                                                self.preview.destroy();
 
909
                                        self.preview = this;
 
910
                                        delete self.loading;
 
911
 
 
912
                                        self.targetWindow( this.targetWindow() );
 
913
                                        self.channel( this.channel() );
 
914
 
 
915
                                        self.send( 'active' );
 
916
                                });
 
917
 
 
918
                                this.send( 'sync', {
 
919
                                        scroll:   self.scroll,
 
920
                                        settings: api.get()
 
921
                                });
 
922
                        });
 
923
 
 
924
                        this.loading.fail( function( reason, location ) {
 
925
                                if ( 'redirect' === reason && location )
 
926
                                        self.previewUrl( location );
 
927
 
 
928
                                if ( 'logged out' === reason ) {
 
929
                                        if ( self.preview ) {
 
930
                                                self.preview.destroy();
 
931
                                                delete self.preview;
 
932
                                        }
 
933
 
 
934
                                        self.login().done( self.refresh );
 
935
                                }
 
936
 
 
937
                                if ( 'cheatin' === reason )
 
938
                                        self.cheatin();
 
939
                        });
 
940
                },
 
941
 
 
942
                login: function() {
 
943
                        var previewer = this,
 
944
                                deferred, messenger, iframe;
 
945
 
 
946
                        if ( this._login )
 
947
                                return this._login;
 
948
 
 
949
                        deferred = $.Deferred();
 
950
                        this._login = deferred.promise();
 
951
 
 
952
                        messenger = new api.Messenger({
 
953
                                channel: 'login',
 
954
                                url:     api.settings.url.login
 
955
                        });
 
956
 
 
957
                        iframe = $('<iframe src="' + api.settings.url.login + '" />').appendTo( this.container );
 
958
 
 
959
                        messenger.targetWindow( iframe[0].contentWindow );
 
960
 
 
961
                        messenger.bind( 'login', function() {
 
962
                                iframe.remove();
 
963
                                messenger.destroy();
 
964
                                delete previewer._login;
 
965
                                deferred.resolve();
 
966
                        });
 
967
 
 
968
                        return this._login;
 
969
                },
 
970
 
 
971
                cheatin: function() {
 
972
                        $( document.body ).empty().addClass('cheatin').append( '<p>' + api.l10n.cheatin + '</p>' );
 
973
                }
 
974
        });
 
975
 
 
976
        api.controlConstructor = {
 
977
                color:  api.ColorControl,
 
978
                upload: api.UploadControl,
 
979
                image:  api.ImageControl,
 
980
                header: api.HeaderControl
 
981
        };
 
982
 
 
983
        $( function() {
 
984
                api.settings = window._wpCustomizeSettings;
 
985
                api.l10n = window._wpCustomizeControlsL10n;
 
986
 
 
987
                // Check if we can run the customizer.
 
988
                if ( ! api.settings )
 
989
                        return;
 
990
 
 
991
                // Redirect to the fallback preview if any incompatibilities are found.
 
992
                if ( ! $.support.postMessage || ( ! $.support.cors && api.settings.isCrossDomain ) )
 
993
                        return window.location = api.settings.url.fallback;
 
994
 
 
995
                var parent, topFocus,
 
996
                        body = $( document.body ),
 
997
                        overlay = body.children( '.wp-full-overlay' ),
 
998
                        title = $( '#customize-info .theme-name.site-title' ),
 
999
                        closeBtn = $( '.customize-controls-close' ),
 
1000
                        saveBtn = $( '#save' );
 
1001
 
 
1002
                // Prevent the form from saving when enter is pressed on an input or select element.
 
1003
                $('#customize-controls').on( 'keydown', function( e ) {
 
1004
                        var isEnter = ( 13 === e.which ),
 
1005
                                $el = $( e.target );
 
1006
 
 
1007
                        if ( isEnter && ( $el.is( 'input:not([type=button])' ) || $el.is( 'select' ) ) ) {
 
1008
                                e.preventDefault();
 
1009
                        }
 
1010
                });
 
1011
 
 
1012
                // Initialize Previewer
 
1013
                api.previewer = new api.Previewer({
 
1014
                        container:   '#customize-preview',
 
1015
                        form:        '#customize-controls',
 
1016
                        previewUrl:  api.settings.url.preview,
 
1017
                        allowedUrls: api.settings.url.allowed,
 
1018
                        signature:   'WP_CUSTOMIZER_SIGNATURE'
 
1019
                }, {
 
1020
 
 
1021
                        nonce: api.settings.nonce,
 
1022
 
 
1023
                        query: function() {
 
1024
                                return {
 
1025
                                        wp_customize: 'on',
 
1026
                                        theme:      api.settings.theme.stylesheet,
 
1027
                                        customized: JSON.stringify( api.get() ),
 
1028
                                        nonce:      this.nonce.preview
 
1029
                                };
 
1030
                        },
 
1031
 
 
1032
                        save: function() {
 
1033
                                var self  = this,
 
1034
                                        query = $.extend( this.query(), {
 
1035
                                                action: 'customize_save',
 
1036
                                                nonce:  this.nonce.save
 
1037
                                        } ),
 
1038
                                        processing = api.state( 'processing' ),
 
1039
                                        submitWhenDoneProcessing,
 
1040
                                        submit;
 
1041
 
 
1042
                                body.addClass( 'saving' );
 
1043
 
 
1044
                                submit = function () {
 
1045
                                        var request = $.post( api.settings.url.ajax, query );
 
1046
 
 
1047
                                        api.trigger( 'save', request );
 
1048
 
 
1049
                                        request.always( function () {
 
1050
                                                body.removeClass( 'saving' );
 
1051
                                        } );
 
1052
 
 
1053
                                        request.done( function( response ) {
 
1054
                                                // Check if the user is logged out.
 
1055
                                                if ( '0' === response ) {
 
1056
                                                        self.preview.iframe.hide();
 
1057
                                                        self.login().done( function() {
 
1058
                                                                self.save();
 
1059
                                                                self.preview.iframe.show();
 
1060
                                                        } );
 
1061
                                                        return;
 
1062
                                                }
 
1063
 
 
1064
                                                // Check for cheaters.
 
1065
                                                if ( '-1' === response ) {
 
1066
                                                        self.cheatin();
 
1067
                                                        return;
 
1068
                                                }
 
1069
 
 
1070
                                                api.trigger( 'saved' );
 
1071
                                        } );
 
1072
                                };
 
1073
 
 
1074
                                if ( 0 === processing() ) {
 
1075
                                        submit();
 
1076
                                } else {
 
1077
                                        submitWhenDoneProcessing = function () {
 
1078
                                                if ( 0 === processing() ) {
 
1079
                                                        api.state.unbind( 'change', submitWhenDoneProcessing );
 
1080
                                                        submit();
 
1081
                                                }
 
1082
                                        };
 
1083
                                        api.state.bind( 'change', submitWhenDoneProcessing );
 
1084
                                }
 
1085
 
 
1086
                        }
 
1087
                });
 
1088
 
 
1089
                // Refresh the nonces if the preview sends updated nonces over.
 
1090
                api.previewer.bind( 'nonce', function( nonce ) {
 
1091
                        $.extend( this.nonce, nonce );
 
1092
                });
 
1093
 
 
1094
                $.each( api.settings.settings, function( id, data ) {
 
1095
                        api.create( id, id, data.value, {
 
1096
                                transport: data.transport,
 
1097
                                previewer: api.previewer
 
1098
                        } );
 
1099
                });
 
1100
 
 
1101
                $.each( api.settings.controls, function( id, data ) {
 
1102
                        var constructor = api.controlConstructor[ data.type ] || api.Control,
 
1103
                                control;
 
1104
 
 
1105
                        control = api.control.add( id, new constructor( id, {
 
1106
                                params: data,
 
1107
                                previewer: api.previewer
 
1108
                        } ) );
 
1109
                });
 
1110
 
 
1111
                // Check if preview url is valid and load the preview frame.
 
1112
                if ( api.previewer.previewUrl() ) {
 
1113
                        api.previewer.refresh();
 
1114
                } else {
 
1115
                        api.previewer.previewUrl( api.settings.url.home );
 
1116
                }
 
1117
 
 
1118
                // Save and activated states
 
1119
                (function() {
 
1120
                        var state = new api.Values(),
 
1121
                                saved = state.create( 'saved' ),
 
1122
                                activated = state.create( 'activated' ),
 
1123
                                processing = state.create( 'processing' );
 
1124
 
 
1125
                        state.bind( 'change', function() {
 
1126
                                if ( ! activated() ) {
 
1127
                                        saveBtn.val( api.l10n.activate ).prop( 'disabled', false );
 
1128
                                        closeBtn.find( '.screen-reader-text' ).text( api.l10n.cancel );
 
1129
 
 
1130
                                } else if ( saved() ) {
 
1131
                                        saveBtn.val( api.l10n.saved ).prop( 'disabled', true );
 
1132
                                        closeBtn.find( '.screen-reader-text' ).text( api.l10n.close );
 
1133
 
 
1134
                                } else {
 
1135
                                        saveBtn.val( api.l10n.save ).prop( 'disabled', false );
 
1136
                                        closeBtn.find( '.screen-reader-text' ).text( api.l10n.cancel );
 
1137
                                }
 
1138
                        });
 
1139
 
 
1140
                        // Set default states.
 
1141
                        saved( true );
 
1142
                        activated( api.settings.theme.active );
 
1143
                        processing( 0 );
 
1144
 
 
1145
                        api.bind( 'change', function() {
 
1146
                                state('saved').set( false );
 
1147
                        });
 
1148
 
 
1149
                        api.bind( 'saved', function() {
 
1150
                                state('saved').set( true );
 
1151
                                state('activated').set( true );
 
1152
                        });
 
1153
 
 
1154
                        activated.bind( function( to ) {
 
1155
                                if ( to )
 
1156
                                        api.trigger( 'activated' );
 
1157
                        });
 
1158
 
 
1159
                        // Expose states to the API.
 
1160
                        api.state = state;
 
1161
                }());
 
1162
 
 
1163
                // Button bindings.
 
1164
                saveBtn.click( function( event ) {
 
1165
                        api.previewer.save();
 
1166
                        event.preventDefault();
 
1167
                }).keydown( function( event ) {
 
1168
                        if ( 9 === event.which ) // tab
 
1169
                                return;
 
1170
                        if ( 13 === event.which ) // enter
 
1171
                                api.previewer.save();
 
1172
                        event.preventDefault();
 
1173
                });
 
1174
 
 
1175
                closeBtn.keydown( function( event ) {
 
1176
                        if ( 9 === event.which ) // tab
 
1177
                                return;
 
1178
                        if ( 13 === event.which ) // enter
 
1179
                                this.click();
 
1180
                        event.preventDefault();
 
1181
                });
 
1182
 
 
1183
                $('.upload-dropzone a.upload').keydown( function( event ) {
 
1184
                        if ( 13 === event.which ) // enter
 
1185
                                this.click();
 
1186
                });
 
1187
 
 
1188
                $('.collapse-sidebar').on( 'click keydown', function( event ) {
 
1189
                        if ( event.type === 'keydown' &&  13 !== event.which ) // enter
 
1190
                                return;
 
1191
 
 
1192
                        overlay.toggleClass( 'collapsed' ).toggleClass( 'expanded' );
 
1193
                        event.preventDefault();
 
1194
                });
 
1195
 
 
1196
                // Bind site title display to the corresponding field.
 
1197
                if ( title.length ) {
 
1198
                        $( '#customize-control-blogname input' ).on( 'input', function() {
 
1199
                                title.text(  this.value );
 
1200
                        } );
 
1201
                }
 
1202
 
 
1203
                // Create a potential postMessage connection with the parent frame.
 
1204
                parent = new api.Messenger({
 
1205
                        url: api.settings.url.parent,
 
1206
                        channel: 'loader'
 
1207
                });
 
1208
 
 
1209
                // If we receive a 'back' event, we're inside an iframe.
 
1210
                // Send any clicks to the 'Return' link to the parent page.
 
1211
                parent.bind( 'back', function() {
 
1212
                        closeBtn.on( 'click.customize-controls-close', function( event ) {
 
1213
                                event.preventDefault();
 
1214
                                parent.send( 'close' );
 
1215
                        });
 
1216
                });
 
1217
 
 
1218
                // Prompt user with AYS dialog if leaving the Customizer with unsaved changes
 
1219
                $( window ).on( 'beforeunload', function () {
 
1220
                        if ( ! api.state( 'saved' )() ) {
 
1221
                                return api.l10n.saveAlert;
 
1222
                        }
 
1223
                } );
 
1224
 
 
1225
                // Pass events through to the parent.
 
1226
                $.each( [ 'saved', 'change' ], function ( i, event ) {
 
1227
                        api.bind( event, function() {
 
1228
                                parent.send( event );
 
1229
                        });
 
1230
                } );
 
1231
 
 
1232
                // When activated, let the loader handle redirecting the page.
 
1233
                // If no loader exists, redirect the page ourselves (if a url exists).
 
1234
                api.bind( 'activated', function() {
 
1235
                        if ( parent.targetWindow() )
 
1236
                                parent.send( 'activated', api.settings.url.activated );
 
1237
                        else if ( api.settings.url.activated )
 
1238
                                window.location = api.settings.url.activated;
 
1239
                });
 
1240
 
 
1241
                // Initialize the connection with the parent frame.
 
1242
                parent.send( 'ready' );
 
1243
 
 
1244
                // Control visibility for default controls
 
1245
                $.each({
 
1246
                        'background_image': {
 
1247
                                controls: [ 'background_repeat', 'background_position_x', 'background_attachment' ],
 
1248
                                callback: function( to ) { return !! to; }
 
1249
                        },
 
1250
                        'show_on_front': {
 
1251
                                controls: [ 'page_on_front', 'page_for_posts' ],
 
1252
                                callback: function( to ) { return 'page' === to; }
 
1253
                        },
 
1254
                        'header_textcolor': {
 
1255
                                controls: [ 'header_textcolor' ],
 
1256
                                callback: function( to ) { return 'blank' !== to; }
 
1257
                        }
 
1258
                }, function( settingId, o ) {
 
1259
                        api( settingId, function( setting ) {
 
1260
                                $.each( o.controls, function( i, controlId ) {
 
1261
                                        api.control( controlId, function( control ) {
 
1262
                                                var visibility = function( to ) {
 
1263
                                                        control.container.toggle( o.callback( to ) );
 
1264
                                                };
 
1265
 
 
1266
                                                visibility( setting.get() );
 
1267
                                                setting.bind( visibility );
 
1268
                                        });
 
1269
                                });
 
1270
                        });
 
1271
                });
 
1272
 
 
1273
                // Juggle the two controls that use header_textcolor
 
1274
                api.control( 'display_header_text', function( control ) {
 
1275
                        var last = '';
 
1276
 
 
1277
                        control.elements[0].unsync( api( 'header_textcolor' ) );
 
1278
 
 
1279
                        control.element = new api.Element( control.container.find('input') );
 
1280
                        control.element.set( 'blank' !== control.setting() );
 
1281
 
 
1282
                        control.element.bind( function( to ) {
 
1283
                                if ( ! to )
 
1284
                                        last = api( 'header_textcolor' ).get();
 
1285
 
 
1286
                                control.setting.set( to ? last : 'blank' );
 
1287
                        });
 
1288
 
 
1289
                        control.setting.bind( function( to ) {
 
1290
                                control.element.set( 'blank' !== to );
 
1291
                        });
 
1292
                });
 
1293
 
 
1294
                api.trigger( 'ready' );
 
1295
 
 
1296
                // Make sure left column gets focus
 
1297
                topFocus = closeBtn;
 
1298
                topFocus.focus();
 
1299
                setTimeout(function () {
 
1300
                        topFocus.focus();
 
1301
                }, 200);
 
1302
 
 
1303
        });
 
1304
 
 
1305
})( wp, jQuery );